Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-HttpContent-Caching-Refactor

This commit is contained in:
Lachlan Roberts 2022-11-02 17:28:42 +11:00
commit 6facedba75
160 changed files with 2382 additions and 2154 deletions

View File

@ -71,7 +71,7 @@ jobs:
# queries: security-extended,security-and-quality
- name: Set up Maven
uses: stCarolas/setup-maven@v4
uses: stCarolas/setup-maven@v.4.5
with:
maven-version: 3.8.6

55
Jenkinsfile vendored
View File

@ -6,6 +6,7 @@ pipeline {
options {
skipDefaultCheckout()
durabilityHint('PERFORMANCE_OPTIMIZED')
buildDiscarder logRotator( numToKeepStr: '60' )
}
stages {
stage("Parallel Stage") {
@ -13,34 +14,32 @@ pipeline {
stage("Build / Test - JDK17") {
agent { node { label 'linux' } }
steps {
container('jetty-build') {
timeout( time: 180, unit: 'MINUTES' ) {
checkout scm
mavenBuild( "jdk17", "clean install javadoc:javadoc -Perrorprone", "maven3")
// Collect up the jacoco execution results (only on main build)
jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class',
exclusionPattern: '' +
// build tools
'**/org/eclipse/jetty/ant/**' +
',*/org/eclipse/jetty/maven/its/**' +
',**/org/eclipse/jetty/its/**' +
// example code / documentation
',**/org/eclipse/jetty/embedded/**' +
',**/org/eclipse/jetty/asyncrest/**' +
',**/org/eclipse/jetty/demo/**' +
// special environments / late integrations
',**/org/eclipse/jetty/gcloud/**' +
',**/org/eclipse/jetty/infinispan/**' +
',**/org/eclipse/jetty/osgi/**' +
',**/org/eclipse/jetty/http/spi/**' +
// test classes
',**/org/eclipse/jetty/tests/**' +
',**/org/eclipse/jetty/test/**',
execPattern: '**/target/jacoco.exec',
classPattern: '**/target/classes',
sourcePattern: '**/src/main/java'
recordIssues id: "jdk17", name: "Static Analysis jdk17", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), errorProne(), spotBugs()]
}
timeout( time: 180, unit: 'MINUTES' ) {
checkout scm
mavenBuild( "jdk17", "clean install javadoc:javadoc -Perrorprone", "maven3")
// Collect up the jacoco execution results (only on main build)
jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class',
exclusionPattern: '' +
// build tools
'**/org/eclipse/jetty/ant/**' +
',*/org/eclipse/jetty/maven/its/**' +
',**/org/eclipse/jetty/its/**' +
// example code / documentation
',**/org/eclipse/jetty/embedded/**' +
',**/org/eclipse/jetty/asyncrest/**' +
',**/org/eclipse/jetty/demo/**' +
// special environments / late integrations
',**/org/eclipse/jetty/gcloud/**' +
',**/org/eclipse/jetty/infinispan/**' +
',**/org/eclipse/jetty/osgi/**' +
',**/org/eclipse/jetty/http/spi/**' +
// test classes
',**/org/eclipse/jetty/tests/**' +
',**/org/eclipse/jetty/test/**',
execPattern: '**/target/jacoco.exec',
classPattern: '**/target/classes',
sourcePattern: '**/src/main/java'
recordIssues id: "jdk17", name: "Static Analysis jdk17", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), errorProne(), spotBugs()]
}
}
}

View File

@ -659,7 +659,7 @@ public class HTTPClientDocs
// Add the new proxy to the list of proxies already registered.
ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
proxyConfig.getProxies().add(proxy);
proxyConfig.addProxy(proxy);
ContentResponse response = httpClient.GET("http://domain.com/path");
// end::proxy[]
@ -684,7 +684,7 @@ public class HTTPClientDocs
// Proxy configuration.
ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
HttpProxy proxy = new HttpProxy("proxy.net", 8080);
proxyConfig.getProxies().add(proxy);
proxyConfig.addProxy(proxy);
ContentResponse response = httpClient.newRequest(serverURI).send();
// end::proxyAuthentication[]

View File

@ -54,7 +54,7 @@ public class WebSocketClientDocs
// Instantiate and configure HttpClient.
HttpClient httpClient = new HttpClient();
// For example, configure a proxy.
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", 8888));
httpClient.getProxyConfiguration().addProxy(new HttpProxy("localhost", 8888));
// Instantiate WebSocketClient, passing HttpClient to the constructor.
WebSocketClient webSocketClient = new WebSocketClient(httpClient);

View File

@ -165,7 +165,7 @@ public class HandlerDocs
public Request.Processor handle(Request request) throws Exception
{
if (HttpMethod.GET.is(request.getMethod()) &&
"greeting".equals(request.getPathInContext()))
"greeting".equals(Request.getPathInContext(request)))
return this;
return null;
}
@ -207,7 +207,7 @@ public class HandlerDocs
{
String name = handler.getClass().getSimpleName().replace("Handler", "");
String path = "/" + name;
if (request.getPathInContext().equals(name))
if (Request.getPathInContext(request).equals(name))
{
Request.Processor processor = handler.handle(request);
if (processor != null)

View File

@ -14,13 +14,14 @@
package org.eclipse.jetty.client;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -30,23 +31,40 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
* Applications add subclasses of {@link Proxy} to this configuration via:
* <pre>
* ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
* proxyConfig.getProxies().add(new HttpProxy(proxyHost, 8080));
* proxyConfig.addProxy(new HttpProxy(proxyHost, 8080));
* </pre>
*
* @see HttpClient#getProxyConfiguration()
*/
public class ProxyConfiguration
{
private final List<Proxy> proxies = new ArrayList<>();
private final List<Proxy> proxies = new BlockingArrayQueue<>();
public List<Proxy> getProxies()
/**
* Adds a proxy.
*
* @param proxy a proxy
* @throws NullPointerException if {@code proxy} is null
*/
public void addProxy(Proxy proxy)
{
return proxies;
proxies.add(Objects.requireNonNull(proxy));
}
/**
* Removes a proxy.
*
* @param proxy a proxy
* @return true if a match is found
*/
public boolean removeProxy(Proxy proxy)
{
return proxies.remove(proxy);
}
public Proxy match(Origin origin)
{
for (Proxy proxy : getProxies())
for (Proxy proxy : proxies)
{
if (proxy.matches(origin))
return proxy;

View File

@ -95,7 +95,7 @@ public class HttpClientCustomProxyTest
// Setup the custom proxy
int proxyPort = connector.getLocalPort();
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
client.getProxyConfiguration().getProxies().add(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
client.getProxyConfiguration().addProxy(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
ContentResponse response = client.newRequest(serverHost, serverPort)
.timeout(5, TimeUnit.SECONDS)

View File

@ -172,7 +172,7 @@ public class HttpClientProxyProtocolTest
EndPoint endPoint = request.getConnectionMetaData().getConnection().getEndPoint();
assertTrue(endPoint instanceof ProxyConnectionFactory.ProxyEndPoint);
ProxyConnectionFactory.ProxyEndPoint proxyEndPoint = (ProxyConnectionFactory.ProxyEndPoint)endPoint;
if (request.getPathInContext().equals("/tls_version"))
if (Request.getPathInContext(request).equals("/tls_version"))
{
assertNotNull(proxyEndPoint.getTLV(typeTLS));
assertEquals(tlsVersion, proxyEndPoint.getAttribute(ProxyConnectionFactory.TLS_VERSION));
@ -231,7 +231,7 @@ public class HttpClientProxyProtocolTest
int proxyPort = connector.getLocalPort();
int serverPort = proxyPort + 1; // Any port will do.
client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
client.getProxyConfiguration().addProxy(new HttpProxy("localhost", proxyPort));
// We are simulating to be a HttpClient inside a proxy.
// The server is configured with the PROXY protocol to know the socket address of clients.

View File

@ -57,7 +57,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
int proxyPort = connector.getLocalPort();
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
client.getProxyConfiguration().addProxy(new HttpProxy("localhost", proxyPort));
ContentResponse response = client.newRequest(serverHost, serverPort)
.scheme(scenario.getScheme())
@ -104,7 +104,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
String proxyHost = "localhost";
int proxyPort = connector.getLocalPort();
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
client.getProxyConfiguration().addProxy(new HttpProxy(proxyHost, proxyPort));
ContentResponse response1 = client.newRequest(serverHost, serverPort)
.scheme(scenario.getScheme())
@ -163,7 +163,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
@Override
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback)
{
String target = request.getPathInContext();
String target = org.eclipse.jetty.server.Request.getPathInContext(request);
if (target.startsWith("/proxy"))
{
String authorization = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION.asString());
@ -202,7 +202,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
});
int proxyPort = connector.getLocalPort();
client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
client.getProxyConfiguration().addProxy(new HttpProxy(proxyHost, proxyPort));
ContentResponse response1 = client.newRequest(serverHost, serverPort)
.scheme(scenario.getScheme())
@ -289,7 +289,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(proxyURI, proxyRealm, "proxyUser", "proxyPassword"));
URI serverURI = URI.create(scenario.getScheme() + "://" + serverHost + ":" + serverPort);
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(serverURI, serverRealm, "serverUser", "serverPassword"));
client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
client.getProxyConfiguration().addProxy(new HttpProxy(proxyHost, proxyPort));
final AtomicInteger requests = new AtomicInteger();
client.getRequestListeners().add(new Request.Listener.Adapter()
{
@ -359,7 +359,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
int serverPort = proxyPort + 1;
URI proxyURI = URI.create(scenario.getScheme() + "://" + proxyHost + ":" + proxyPort);
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(proxyURI, proxyRealm, "proxyUser", "proxyPassword"));
client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
client.getProxyConfiguration().addProxy(new HttpProxy(proxyHost, proxyPort));
final AtomicInteger requests = new AtomicInteger();
client.getRequestListeners().add(new Request.Listener.Adapter()
{

View File

@ -445,7 +445,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Override
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
{
if (request.getPathInContext().startsWith("/redirect"))
if (Request.getPathInContext(request).startsWith("/redirect"))
{
response.setStatus(HttpStatus.SEE_OTHER_303);
response.getHeaders().put(HttpHeader.LOCATION, scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/ok");
@ -537,7 +537,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
if ("/one".equals(target))
{
response.setStatus(HttpStatus.SEE_OTHER_303);
@ -588,7 +588,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception
{
String serverURI = scenario.getScheme() + "://localhost:" + connector.getLocalPort();
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
if ("/one".equals(target))
{
Thread.sleep(timeout);
@ -676,7 +676,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
{
Fields fields = Request.extractQueryParameters(request);
String[] paths = request.getPathInContext().split("/", 4);
String[] paths = Request.getPathInContext(request).split("/", 4);
int status = Integer.parseInt(paths[1]);
response.setStatus(status);

View File

@ -484,7 +484,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response)
{
if (request.getPathInContext().endsWith("/one"))
if (org.eclipse.jetty.server.Request.getPathInContext(request).endsWith("/one"))
request.getConnectionMetaData().getConnection().getEndPoint().close();
}
});
@ -904,7 +904,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response)
{
List<String> userAgents = request.getHeaders().getValuesList(HttpHeader.USER_AGENT);
if ("/ua".equals(request.getPathInContext()))
if ("/ua".equals(org.eclipse.jetty.server.Request.getPathInContext(request)))
assertEquals(1, userAgents.size());
else
assertEquals(0, userAgents.size());

View File

@ -203,7 +203,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, Response response)
{
assertEquals(path, request.getPathInContext());
assertEquals(path, org.eclipse.jetty.server.Request.getPathInContext(request));
}
});
@ -236,7 +236,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, Response response)
{
assertEquals(path, request.getPathInContext());
assertEquals(path, org.eclipse.jetty.server.Request.getPathInContext(request));
assertEquals(query, request.getHttpURI().getQuery());
}
});
@ -273,7 +273,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, Response response)
{
assertEquals(path, request.getPathInContext());
assertEquals(path, org.eclipse.jetty.server.Request.getPathInContext(request));
assertEquals(query, request.getHttpURI().getQuery());
}
});
@ -312,7 +312,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, Response response)
{
assertEquals(path, request.getPathInContext());
assertEquals(path, org.eclipse.jetty.server.Request.getPathInContext(request));
assertEquals(query, request.getHttpURI().getQuery());
}
});
@ -354,7 +354,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, Response response)
{
assertEquals(path, request.getPathInContext());
assertEquals(path, org.eclipse.jetty.server.Request.getPathInContext(request));
assertEquals(query, request.getHttpURI().getQuery());
Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request);
assertEquals(value1, fields.getValue(name1));
@ -393,7 +393,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, Response response)
{
assertEquals(path, request.getPathInContext());
assertEquals(path, org.eclipse.jetty.server.Request.getPathInContext(request));
assertEquals(query, request.getHttpURI().getQuery());
}
});
@ -426,7 +426,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, Response response)
{
assertEquals(path, request.getPathInContext());
assertEquals(path, org.eclipse.jetty.server.Request.getPathInContext(request));
assertEquals(query, request.getHttpURI().getQuery());
}
});
@ -607,7 +607,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
protected void service(org.eclipse.jetty.server.Request request, Response response)
{
assertEquals("*", request.getHttpURI().getPath());
assertEquals("*", request.getPathInContext());
assertEquals("*", org.eclipse.jetty.server.Request.getPathInContext(request));
}
});

View File

@ -179,7 +179,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
int r = (int)request.getHeaders().getLongField(headerName);
if ("/foo".equals(target) && r == 0)
{
@ -234,7 +234,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
int r = (int)request.getHeaders().getLongField(headerName);
if ("/foo/bar".equals(target) && r == 0)
{
@ -290,7 +290,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
int r = (int)request.getHeaders().getLongField(headerName);
if ("/foo".equals(target) && r == 0)
{
@ -346,7 +346,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
int r = (int)request.getHeaders().getLongField(headerName);
if ("/foo/bar".equals(target) && r == 0)
{
@ -403,7 +403,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
int r = (int)request.getHeaders().getLongField(headerName);
if ("/foo".equals(target) && r == 0)
{
@ -462,7 +462,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
int r = (int)request.getHeaders().getLongField(headerName);
if ("/foo".equals(target) && r == 0)
{
@ -528,7 +528,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
int r = (int)request.getHeaders().getLongField(headerName);
if ("/foo".equals(target) && r == 0)
{
@ -598,7 +598,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
protected void service(Request request, org.eclipse.jetty.server.Response response)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
int r = (int)request.getHeaders().getLongField(headerName);
if ("/foo/bar".equals(target) && r == 0)
{

View File

@ -485,7 +485,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
@Override
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback)
{
if ("/done".equals(request.getPathInContext()))
if ("/done".equals(org.eclipse.jetty.server.Request.getPathInContext(request)))
callback.succeeded();
else
Response.sendRedirect(request, response, callback, "/done");

View File

@ -54,7 +54,7 @@ public class Socks4ProxyTest
public void prepare() throws Exception
{
proxy = ServerSocketChannel.open();
proxy.bind(new InetSocketAddress("localhost", 0));
proxy.bind(new InetSocketAddress("127.0.0.1", 0));
ClientConnector connector = new ClientConnector();
QueuedThreadPool clientThreads = new QueuedThreadPool();
@ -77,7 +77,7 @@ public class Socks4ProxyTest
public void testSocks4Proxy() throws Exception
{
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy("localhost", proxyPort));
client.getProxyConfiguration().addProxy(new Socks4Proxy("127.0.0.1", proxyPort));
CountDownLatch latch = new CountDownLatch(1);
@ -139,7 +139,7 @@ public class Socks4ProxyTest
public void testSocks4ProxyWithSplitResponse() throws Exception
{
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy("localhost", proxyPort));
client.getProxyConfiguration().addProxy(new Socks4Proxy("127.0.0.1", proxyPort));
CountDownLatch latch = new CountDownLatch(1);
@ -196,7 +196,6 @@ public class Socks4ProxyTest
@Test
public void testSocks4ProxyWithTLSServer() throws Exception
{
String proxyHost = "localhost";
int proxyPort = proxy.socket().getLocalPort();
String serverHost = "127.0.0.13"; // Server host different from proxy host.
@ -215,7 +214,7 @@ public class Socks4ProxyTest
// The hostname must be that of the server, not of the proxy.
ssl.setHostnameVerifier((hostname, session) -> serverHost.equals(hostname));
});
client.getProxyConfiguration().getProxies().add(new Socks4Proxy(proxyHost, proxyPort));
client.getProxyConfiguration().addProxy(new Socks4Proxy("127.0.0.1", proxyPort));
CountDownLatch latch = new CountDownLatch(1);
client.newRequest(serverHost, serverPort)
@ -281,12 +280,15 @@ public class Socks4ProxyTest
@Test
public void testRequestTimeoutWhenSocksProxyDoesNotRespond() throws Exception
{
String proxyHost = "localhost";
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy(proxyHost, proxyPort));
client.getProxyConfiguration().addProxy(new Socks4Proxy("127.0.0.1", proxyPort));
long timeout = 1000;
Request request = client.newRequest("localhost", proxyPort + 1)
// Use an address to avoid resolution of "localhost" to multiple addresses.
String serverHost = "127.0.0.13";
int serverPort = proxyPort + 1; // Any port will do
Request request = client.newRequest(serverHost, serverPort)
.timeout(timeout, TimeUnit.MILLISECONDS);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
@ -303,13 +305,15 @@ public class Socks4ProxyTest
@Test
public void testIdleTimeoutWhenSocksProxyDoesNotRespond() throws Exception
{
String proxyHost = "localhost";
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy(proxyHost, proxyPort));
client.getProxyConfiguration().addProxy(new Socks4Proxy("127.0.0.1", proxyPort));
long idleTimeout = 1000;
client.setIdleTimeout(idleTimeout);
Request request = client.newRequest("localhost", proxyPort + 1);
// Use an address to avoid resolution of "localhost" to multiple addresses.
String serverHost = "127.0.0.13";
int serverPort = proxyPort + 1; // Any port will do
Request request = client.newRequest(serverHost, serverPort);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
@ -325,11 +329,13 @@ public class Socks4ProxyTest
@Test
public void testSocksProxyClosesConnectionImmediately() throws Exception
{
String proxyHost = "localhost";
int proxyPort = proxy.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy(proxyHost, proxyPort));
client.getProxyConfiguration().addProxy(new Socks4Proxy("127.0.0.1", proxyPort));
Request request = client.newRequest("localhost", proxyPort + 1);
// Use an address to avoid resolution of "localhost" to multiple addresses.
String serverHost = "127.0.0.13";
int serverPort = proxyPort + 1; // Any port will do
Request request = client.newRequest(serverHost, serverPort);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);

View File

@ -73,7 +73,7 @@ public class ValidatingConnectionPoolTest extends AbstractHttpClientServerTest
@Override
protected void service(org.eclipse.jetty.server.Request request, Response response) throws Throwable
{
if (request.getPathInContext().endsWith("/redirect"))
if (org.eclipse.jetty.server.Request.getPathInContext(request).endsWith("/redirect"))
{
response.setStatus(HttpStatus.TEMPORARY_REDIRECT_307);
response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, 0);

View File

@ -23,13 +23,7 @@
<Arg>starting</Arg>
<Arg>customise</Arg>
</Call>
<Call name="addLifeCycleBinding">
<Arg>
<New class="org.eclipse.jetty.deploy.bindings.DebugBinding">
<Arg>customise</Arg>
</New>
</Arg>
</Call> -->
-->
</New>
</Arg>

View File

@ -91,7 +91,7 @@ public class FastCGIProxyHandlerTest
{
HttpURI httpURI = request.getHttpURI();
HttpURI.Mutable newHttpURI = HttpURI.build(httpURI)
.path(appContextPath + request.getPathInContext());
.path(appContextPath + Request.getPathInContext(request));
newHttpURI.port(unixDomainPath == null ? ((ServerConnector)serverConnector).getLocalPort() : 0);
return newHttpURI;
}, "/scriptRoot");
@ -152,7 +152,7 @@ public class FastCGIProxyHandlerTest
public void process(Request request, Response response, Callback callback)
{
assertNotEquals(proxyContext.getContextPath(), request.getContext().getContextPath());
assertEquals(path, request.getPathInContext());
assertEquals(path, Request.getPathInContext(request));
response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, data.length);
response.write(true, ByteBuffer.wrap(data), callback);
}
@ -198,7 +198,7 @@ public class FastCGIProxyHandlerTest
{
assertThat((String)request.getAttribute(FCGI.Headers.REQUEST_URI), startsWith(originalPath));
assertEquals(originalQuery, request.getAttribute(FCGI.Headers.QUERY_STRING));
assertThat(request.getPathInContext(), endsWith(remotePath));
assertThat(Request.getPathInContext(request), endsWith(remotePath));
callback.succeeded();
}
});
@ -211,7 +211,7 @@ public class FastCGIProxyHandlerTest
@Override
public Request.Processor handle(Request request) throws Exception
{
if (request.getPathInContext().startsWith("/remote/"))
if (Request.getPathInContext(request).startsWith("/remote/"))
{
request.setAttribute(pathAttribute, originalPath);
request.setAttribute(queryAttribute, originalQuery);

View File

@ -875,7 +875,16 @@ public interface HttpURI
{
_param = param;
if (_path != null && _param != null)
{
int lastSlash = _path.lastIndexOf('/');
if (lastSlash >= 0)
{
int trailingParam = _path.indexOf(';', lastSlash + 1);
if (trailingParam >= 0)
_path = _path.substring(0, trailingParam);
}
_path += ";" + _param;
}
_uri = null;
return this;
@ -889,9 +898,22 @@ public interface HttpURI
{
if (hasAuthority() && !isPathValidForAuthority(path))
throw new IllegalArgumentException("Relative path with authority");
if (!URIUtil.isPathValid(path))
throw new IllegalArgumentException("Path not correctly encoded: " + path);
_uri = null;
_path = path;
_canonicalPath = null;
// If the passed path does not have a parameter, then keep the current parameter
// else delete the current parameter
if (_param != null)
{
if (path.indexOf(';') >= 0)
_param = null;
else
_path = _path + ';' + _param;
}
return this;
}

View File

@ -21,6 +21,7 @@ import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -88,6 +89,11 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
_suffixMap.clear();
}
public Stream<MappedResource<E>> streamResources()
{
return _mappings.stream();
}
public void removeIf(Predicate<MappedResource<E>> predicate)
{
_mappings.removeIf(predicate);

View File

@ -916,4 +916,17 @@ public class HttpURITest
.path("");
assertEquals("//host", uri.asString());
}
@Test
public void testKeepParam()
{
HttpURI orig = HttpURI.from("http://localhost/context/info;param=value");
HttpURI built = HttpURI.build(orig).path("/context/info").asImmutable();
assertThat(built.getParam(), is(orig.getParam()));
assertThat(built.toString(), is(orig.toString()));
built = HttpURI.build(orig).path("/context/info").param("param=value").asImmutable();
assertThat(built.getParam(), is(orig.getParam()));
assertThat(built.toString(), is(orig.toString()));
}
}

View File

@ -519,6 +519,10 @@ public class MultiPartTest
{
byte[] random = new byte[8192];
ThreadLocalRandom.current().nextBytes(random);
// Make sure the last 2 bytes are not \r\n,
// otherwise the multipart parser gets confused.
random[random.length - 2] = 0;
random[random.length - 1] = 0;
TestPartsListener listener = new TestPartsListener();
MultiPart.Parser parser = new MultiPart.Parser("BOUNDARY", listener);
@ -526,7 +530,7 @@ public class MultiPartTest
String preamble = "Blah blah blah\r\n--BOUNDARY\r\n\r\n";
parser.parse(Content.Chunk.from(BufferUtil.toBuffer(preamble), false));
parser.parse(Content.Chunk.from(ByteBuffer.wrap(random), false));
String epilogue = "\r\n--BOUNDARY\r\nBlah blah blah!\r\n";
String epilogue = "\r\n--BOUNDARY--\r\nBlah blah blah!\r\n";
ByteBuffer epilogueBuffer = BufferUtil.toBuffer(epilogue);
parser.parse(Content.Chunk.from(epilogueBuffer, true));

View File

@ -25,8 +25,6 @@ import org.eclipse.jetty.util.Promise;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ConnectTimeoutTest extends AbstractTest
@ -49,7 +47,6 @@ public class ConnectTimeoutTest extends AbstractTest
@Override
public void failed(Throwable x)
{
assertThat(x, instanceOf(SocketTimeoutException.class));
latch.countDown();
}
});

View File

@ -67,7 +67,7 @@ public class HTTP2CServer extends Server
response.getHeaders().put("Custom", "Value");
response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain");
String content = "Hello from Jetty using " + request.getConnectionMetaData().getProtocol() + "\n";
content += "uri=" + request.getPathInContext() + "\n";
content += "uri=" + Request.getPathInContext(request) + "\n";
content += "date=" + new Date() + "\n";
response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, content.length());
Content.Sink.write(response, true, content, callback);

View File

@ -422,7 +422,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
});
int proxyPort = connector.getLocalPort();
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(new Origin.Address("localhost", proxyPort), false, new Origin.Protocol(List.of("h2c"), false)));
httpClient.getProxyConfiguration().addProxy(new HttpProxy(new Origin.Address("localhost", proxyPort), false, new Origin.Protocol(List.of("h2c"), false)));
int serverPort = proxyPort + 1; // Any port will do, just not the same as the proxy.
ContentResponse response = httpClient.newRequest("localhost", serverPort)

View File

@ -421,7 +421,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest
@Override
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
if (request.getPathInContext().endsWith("/1"))
if (Request.getPathInContext(request).endsWith("/1"))
sleep(2 * timeout);
callback.succeeded();
}

View File

@ -135,7 +135,7 @@ public class MultiplexedConnectionPoolTest
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
int req = Integer.parseInt(request.getPathInContext().substring(1));
int req = Integer.parseInt(Request.getPathInContext(request).substring(1));
reqExecutingLatches[req].countDown();
Thread.sleep(250);
reqExecutedLatches[req].countDown();
@ -231,7 +231,7 @@ public class MultiplexedConnectionPoolTest
@Override
public void process(Request request, Response response, Callback callback)
{
int req = Integer.parseInt(request.getPathInContext().substring(1));
int req = Integer.parseInt(Request.getPathInContext(request).substring(1));
Content.Sink.write(response, true, "req " + req + " executed", callback);
}
}, 64, 1L);
@ -380,7 +380,7 @@ public class MultiplexedConnectionPoolTest
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
if (request.getPathInContext().equals("/block"))
if (Request.getPathInContext(request).equals("/block"))
{
handlerSignalingSemaphore.release();
handlerWaitingSemaphore.acquire();

View File

@ -108,7 +108,7 @@ public class PushedResourcesTest extends AbstractTest
@Override
public void process(Request request, Response response, Callback callback)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
if (target.equals(path1))
{
response.write(true, ByteBuffer.wrap(pushBytes1), callback);
@ -173,7 +173,7 @@ public class PushedResourcesTest extends AbstractTest
@Override
public void process(Request request, Response response, Callback callback)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
if (target.equals(oldPath))
{
Response.sendRedirect(request, response, callback, newPath);

View File

@ -928,7 +928,7 @@ public class StreamResetTest extends AbstractTest
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
if (target.equals("/1"))
service1(request, response, callback);
else if (target.equals("/2"))

View File

@ -480,7 +480,7 @@ public abstract class ProxyHandler extends Handler.Processor
* <p>Forward proxies are configured in client applications that use
* {@link HttpClient} in this way:</p>
* <pre>{@code
* httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
* httpClient.getProxyConfiguration().addProxy(new HttpProxy(proxyHost, proxyPort));
* }</pre>
*
* @see org.eclipse.jetty.client.ProxyConfiguration

View File

@ -257,7 +257,7 @@ public class ForwardProxyWithDynamicTransportTest
int proxyPort = proxySecure ? proxyTLSConnector.getLocalPort() : proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy proxy = new HttpProxy(proxyAddress, proxySecure, proxyProtocol);
client.getProxyConfiguration().getProxies().add(proxy);
client.getProxyConfiguration().addProxy(proxy);
String scheme = serverSecure ? "https" : "http";
int serverPort = serverSecure ? serverTLSConnector.getLocalPort() : serverConnector.getLocalPort();
@ -293,7 +293,7 @@ public class ForwardProxyWithDynamicTransportTest
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy proxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(proxy);
client.getProxyConfiguration().addProxy(proxy);
long idleTimeout = 1000;
http2Client.setStreamIdleTimeout(idleTimeout);
@ -334,7 +334,7 @@ public class ForwardProxyWithDynamicTransportTest
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(httpProxy);
client.getProxyConfiguration().addProxy(httpProxy);
proxy.stop();
CountDownLatch latch = new CountDownLatch(1);
@ -369,7 +369,7 @@ public class ForwardProxyWithDynamicTransportTest
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(httpProxy);
client.getProxyConfiguration().addProxy(httpProxy);
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())

View File

@ -8,7 +8,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-quic-quiche-foreign-incubator</artifactId>
<name>Jetty Core :: QUIC :: Quiche :: Foreign Binding (incubator)</name>
<name>Jetty Core :: QUIC :: Quiche :: Foreign (Java 17)</name>
<properties>
<bundle-symbolic-name>${project.groupId}.quic-quiche-foreign-incubator</bundle-symbolic-name>
@ -20,9 +20,6 @@
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<release>17</release>
<compilerArgs>
<arg>--add-modules</arg>
<arg>jdk.incubator.foreign</arg>

View File

@ -14,17 +14,6 @@
<modules>
<module>jetty-quic-quiche-common</module>
<module>jetty-quic-quiche-jna</module>
<module>jetty-quic-quiche-foreign-incubator</module>
</modules>
<profiles>
<profile>
<id>jdk17</id>
<activation>
<jdk>17</jdk>
</activation>
<modules>
<module>jetty-quic-quiche-foreign-incubator</module>
</modules>
</profile>
</profiles>
</project>

View File

@ -42,7 +42,7 @@
<profile>
<id>jdk17</id>
<activation>
<jdk>[17,)</jdk>
<jdk>17</jdk>
</activation>
<dependencies>
<dependency>

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.rewrite.handler;
import java.io.IOException;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.URIUtil;
@ -25,20 +26,22 @@ import org.eclipse.jetty.util.URIUtil;
public class CompactPathRule extends Rule
{
@Override
public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException
public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor request) throws IOException
{
String path = input.getPathInContext();
String path = request.getHttpURI().getCanonicalPath();
String compacted = URIUtil.compactPath(path);
if (path.equals(compacted))
return null;
return new Request.WrapperProcessor(input)
HttpURI uri = Request.newHttpURIFrom(request, compacted);
return new Request.WrapperProcessor(request)
{
@Override
public String getPathInContext()
public HttpURI getHttpURI()
{
return compacted;
return uri;
}
};
}

View File

@ -43,7 +43,7 @@ public class CookiePatternRuleTest extends AbstractRuleTest
public void process(Request request, Response response, Callback callback)
{
response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain;charset=utf-8");
Content.Sink.write(response, false, "pathInContext=%s%n".formatted(request.getPathInContext()), Callback.NOOP);
Content.Sink.write(response, false, "pathInContext=%s%n".formatted(Request.getPathInContext(request)), Callback.NOOP);
Content.Sink.write(response, false, "path=%s%n".formatted(request.getHttpURI().getPath()), Callback.NOOP);
Content.Sink.write(response, false, "query=%s%n".formatted(request.getHttpURI().getQuery()), Callback.NOOP);
Request original = Request.unWrap(request);

View File

@ -78,83 +78,81 @@ public interface ConnectionMetaData extends Attributes
class Wrapper extends Attributes.Wrapper implements ConnectionMetaData
{
private final ConnectionMetaData _wrapped;
public Wrapper(ConnectionMetaData wrapped)
{
super(wrapped);
_wrapped = wrapped;
}
protected ConnectionMetaData getWrappedConnectionMetaData()
@Override
public ConnectionMetaData getWrapped()
{
return _wrapped;
return (ConnectionMetaData)super.getWrapped();
}
@Override
public String getId()
{
return _wrapped.getId();
return getWrapped().getId();
}
@Override
public HttpConfiguration getHttpConfiguration()
{
return _wrapped.getHttpConfiguration();
return getWrapped().getHttpConfiguration();
}
@Override
public HttpVersion getHttpVersion()
{
return _wrapped.getHttpVersion();
return getWrapped().getHttpVersion();
}
@Override
public String getProtocol()
{
return _wrapped.getProtocol();
return getWrapped().getProtocol();
}
@Override
public Connection getConnection()
{
return _wrapped.getConnection();
return getWrapped().getConnection();
}
@Override
public Connector getConnector()
{
return _wrapped.getConnector();
return getWrapped().getConnector();
}
@Override
public boolean isPersistent()
{
return _wrapped.isPersistent();
return getWrapped().isPersistent();
}
@Override
public boolean isSecure()
{
return _wrapped.isSecure();
return getWrapped().isSecure();
}
@Override
public SocketAddress getRemoteSocketAddress()
{
return _wrapped.getRemoteSocketAddress();
return getWrapped().getRemoteSocketAddress();
}
@Override
public SocketAddress getLocalSocketAddress()
{
return _wrapped.getLocalSocketAddress();
return getWrapped().getLocalSocketAddress();
}
@Override
public HostPort getServerAuthority()
{
return _wrapped.getServerAuthority();
return getWrapped().getServerAuthority();
}
}
}

View File

@ -35,7 +35,7 @@ import org.eclipse.jetty.util.resource.Resource;
public interface Context extends Attributes, Decorator, Executor
{
/**
* @return The URI path prefix of the context, which may be null for the server context, or "/" for the root context.
* @return the context path of this Context
*/
String getContextPath();
@ -56,4 +56,15 @@ public interface Context extends Attributes, Decorator, Executor
/** scope the calling thread to the context and request and run the runnable. */
void run(Runnable runnable, Request request);
/**
* <p>Returns a URI path scoped to this Context.</p>
* <p>For example, if the context path is {@code /ctx} then a
* full path of {@code /ctx/foo/bar} will return {@code /foo/bar}.</p>
*
* @param fullPath A full URI path
* @return The URI path scoped to this Context, or {@code null} if the full path does not match this Context.
* The empty string is returned if the full path is exactly the context path.
*/
String getPathInContext(String fullPath);
}

View File

@ -1139,7 +1139,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
Resource baseResource = context.getBaseResource();
if (baseResource != null)
{
String fileName = baseResource.resolve(request.getPathInContext()).getName();
String fileName = baseResource.resolve(Request.getPathInContext(request)).getName();
append(b, fileName);
}
else

View File

@ -597,7 +597,7 @@ public class ForwardedRequestCustomizer implements HttpConfiguration.Customizer
getId(),
remote,
authority,
getWrappedConnectionMetaData()
getWrapped()
);
}
};

View File

@ -1,71 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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.server;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.util.Callback;
/**
* Abstraction of the outbound HTTP transport.
*/
public interface HttpTransport
{
String UPGRADE_CONNECTION_ATTRIBUTE = HttpTransport.class.getName() + ".UPGRADE";
/**
* Asynchronous call to send a response (or part) over the transport
*
* @param request True if the response if for a HEAD request (and the data should not be sent).
* @param response The header info to send, or null if just sending more data.
* The first call to send for a response must have a non null info.
* @param content A buffer of content to be sent.
* @param lastContent True if the content is the last content for the current response.
* @param callback The Callback instance that success or failure of the send is notified on
*/
void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback);
/**
* @return true if responses can be pushed over this transport
*/
boolean isPushSupported();
/**
* @param request A request to use as the basis for generating a pushed response.
*/
void push(MetaData.Request request);
/**
* Called to indicated the end of the current request/response cycle (which may be
* some time after the last content is sent).
*/
void onCompleted();
/**
* Aborts this transport.
* <p>
* This method should terminate the transport in a way that
* can indicate an abnormal response to the client, for example
* by abruptly close the connection.
* <p>
* This method is called when an error response needs to be sent,
* but the response is already committed, or when a write failure
* is detected. If abort is called, {@link #onCompleted()} is not
* called
*
* @param failure the failure that caused the abort.
*/
void abort(Throwable failure);
}

View File

@ -121,6 +121,7 @@ public interface Request extends Attributes, Content.Source
/**
* an ID unique within the lifetime scope of the {@link ConnectionMetaData#getId()}).
* This may be a protocol ID (eg HTTP/2 stream ID) or it may be unrelated to the protocol.
*
* @see HttpStream#getId()
*/
String getId();
@ -142,6 +143,8 @@ public interface Request extends Attributes, Content.Source
/**
* @return the HTTP URI of this request
* @see #getContextPath(Request)
* @see #getPathInContext(Request)
*/
HttpURI getHttpURI();
@ -151,10 +154,31 @@ public interface Request extends Attributes, Content.Source
Context getContext();
/**
* TODO see discussion in #7713, as this path should probably be canonically encoded - ie everything but %25 and %2F decoded
* @return The part of the decoded path of the URI after any context path prefix has been removed.
* <p>Returns the context path of this Request.</p>
* <p>This is equivalent to {@code request.getContext().getContextPath()}.</p>
*
* @param request The request to get the context path from.
* @return The contextPath of the request.
* @see Context#getContextPath()
*/
String getPathInContext();
static String getContextPath(Request request)
{
return request.getContext().getContextPath();
}
/**
* <p>Returns the canonically encoded path of the URI, scoped to the current context.</p>
* <p>For example, when the request has a {@link Context} with {@code contextPath=/ctx} and the request's
* {@link HttpURI} canonical path is {@code canonicalPath=/ctx/foo}, then {@code pathInContext=/foo}.</p>
*
* @return The part of the canonically encoded path of the URI after any context path prefix has been removed.
* @see HttpURI#getCanonicalPath()
* @see Context#getContextPath()
*/
static String getPathInContext(Request request)
{
return request.getContext().getPathInContext(request.getHttpURI().getCanonicalPath());
}
/**
* @return the HTTP headers of this request
@ -509,12 +533,6 @@ public interface Request extends Attributes, Content.Source
return getWrapped().getContext();
}
@Override
public String getPathInContext()
{
return getWrapped().getPathInContext();
}
@Override
public HttpFields getHeaders()
{
@ -689,4 +707,21 @@ public interface Request extends Attributes, Content.Source
processor.process(this, response, callback);
}
}
/**
* <p>Creates a new {@link HttpURI} from the given Request's HttpURI and the given path in context.</p>
* <p>For example, for {@code contextPath=/ctx}, {@code request.httpURI=http://host/ctx/path?a=b}, and
* {@code newPathInContext=/newPath}, the returned HttpURI is {@code http://host/ctx/newPath?a=b}.</p>
*
* @param request The request to base the new HttpURI on.
* @param newPathInContext The new path in context for the new HttpURI
* @return A new immutable HttpURI with the path in context replaced, but query string and path
* parameters retained.
*/
static HttpURI newHttpURIFrom(Request request, String newPathInContext)
{
return HttpURI.build(request.getHttpURI())
.path(URIUtil.addPaths(getContextPath(request), newPathInContext))
.asImmutable();
}
}

View File

@ -175,7 +175,7 @@ public class ResourceService
public void doGet(Request request, Response response, Callback callback, HttpContent content) throws Exception
{
String pathInContext = request.getPathInContext();
String pathInContext = Request.getPathInContext(request);
// Is this a Range request?
List<String> reqRanges = request.getHeaders().getValuesList(HttpHeader.RANGE.asString());
@ -457,10 +457,7 @@ public class ResourceService
HttpURI.Mutable uri = HttpURI.build(request.getHttpURI());
if (!uri.getCanonicalPath().endsWith("/"))
{
// TODO need URI util that handles param and query without reconstructing entire URI with scheme and authority
String parameter = uri.getParam();
uri.path(uri.getCanonicalPath() + "/");
uri.param(parameter);
response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, 0);
// TODO: can writeRedirect (override) also work for WelcomeActionType.REDIRECT?
sendRedirect(request, response, callback, uri.getPathQuery());
@ -544,11 +541,8 @@ public class ResourceService
{
// Redirect to the index
response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, 0);
// TODO need URI util that handles param and query without reconstructing entire URI with scheme and authority
HttpURI.Mutable uri = HttpURI.build(request.getHttpURI());
String parameter = uri.getParam();
uri.path(URIUtil.addPaths(contextPath, welcomeTarget));
uri.param(parameter);
return new WelcomeAction(WelcomeActionType.REDIRECT, uri.getPathQuery());
}

View File

@ -712,7 +712,7 @@ public class Server extends Handler.Wrapper implements Attributes
private static class DynamicErrorProcessor extends ErrorProcessor {}
private class ServerContext extends Attributes.Wrapper implements Context
class ServerContext extends Attributes.Wrapper implements Context
{
private ServerContext()
{
@ -785,6 +785,12 @@ public class Server extends Handler.Wrapper implements Attributes
if (factory != null)
factory.destroy(o);
}
@Override
public String getPathInContext(String fullPath)
{
return fullPath;
}
}
private class ServerEnvironment extends Attributes.Wrapper implements Environment

View File

@ -129,7 +129,7 @@ public class BufferedResponseHandler extends Handler.Wrapper
if (processor == null)
return null;
final String path = request.getPathInContext();
final String path = Request.getPathInContext(request);
if (LOG.isDebugEnabled())
LOG.debug("{} handle {} in {}", this, request, request.getContext());

View File

@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.EventListener;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
@ -94,6 +95,7 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
private String _displayName;
private String _contextPath = "/";
private boolean _rootContext = true;
private Resource _baseResource;
private ClassLoader _classLoader;
private Request.Processor _errorProcessor;
@ -593,20 +595,6 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
return false;
}
protected String getPathInContext(Request request)
{
String path = request.getPathInContext();
if (!path.startsWith(_context.getContextPath()))
return null;
if ("/".equals(_context.getContextPath()))
return path;
if (path.length() == _context.getContextPath().length())
return "";
if (path.charAt(_context.getContextPath().length()) != '/')
return null;
return path.substring(_context.getContextPath().length());
}
@Override
public void destroy()
{
@ -622,27 +610,45 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
if (!checkVirtualHost(request))
return null;
String pathInContext = getPathInContext(request);
if (pathInContext == null)
return null;
// The root context handles all requests.
if (!_rootContext)
{
// Otherwise match the path.
String path = request.getHttpURI().getCanonicalPath();
if (path == null || !path.startsWith(_contextPath))
return null;
if (pathInContext.isEmpty() && !getAllowNullPathInContext())
return this::processMovedPermanently;
if (path.length() == _contextPath.length())
{
if (!getAllowNullPathInContext())
return this::processMovedPermanently;
}
else
{
if (path.charAt(_contextPath.length()) != '/')
return null;
}
}
// TODO check availability and maybe return a 503
if (!isAvailable() && isStarted())
return this::processUnavailable;
ContextRequest contextRequest = wrap(request, pathInContext);
ContextRequest contextRequest = wrap(request);
// wrap might fail (eg ServletContextHandler could not match a servlet)
if (contextRequest == null)
return null;
// Does this handler want to process the request itself?
Request.Processor processor = processByContextHandler(contextRequest);
if (processor != null)
return processor;
return contextRequest.wrapProcessor(_context.get(contextRequest, contextRequest));
// The contextRequest is-a Supplier<Processor> that calls effectively calls getHandler().handle(request).
// Call this supplier in the scope of the context.
Request.Processor contextScopedProcessor = _context.get(contextRequest, contextRequest);
// Wrap the contextScopedProcessor with a wrapper that uses the wrapped request
return contextRequest.wrapProcessor(contextScopedProcessor);
}
protected void processMovedPermanently(Request request, Response response, Callback callback)
@ -665,7 +671,7 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
protected Request.Processor processByContextHandler(ContextRequest contextRequest)
{
if (!_allowNullPathInContext && StringUtil.isEmpty(contextRequest.getPathInContext()))
if (!_allowNullPathInContext && StringUtil.isEmpty(Request.getPathInContext(contextRequest)))
{
return (request, response, callback) ->
{
@ -687,7 +693,8 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
{
if (isStarted())
throw new IllegalStateException(getState());
_contextPath = URIUtil.canonicalPath(contextPath);
_contextPath = URIUtil.canonicalPath(Objects.requireNonNull(contextPath));
_rootContext = "/".equals(contextPath);
}
/**
@ -777,9 +784,9 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
_errorProcessor = errorProcessor;
}
protected ContextRequest wrap(Request request, String pathInContext)
protected ContextRequest wrap(Request request)
{
return new ContextRequest(this, _context, request, pathInContext);
return new ContextRequest(this, _context, request);
}
@Override
@ -1163,6 +1170,20 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
if (factory != null)
factory.destroy(o);
}
@Override
public String getPathInContext(String fullPath)
{
if (_rootContext)
return fullPath;
if (!fullPath.startsWith(_contextPath))
return null;
if (fullPath.length() == _contextPath.length())
return "";
if (fullPath.charAt(_contextPath.length()) != '/')
return null;
return fullPath.substring(_contextPath.length());
}
}
/**

View File

@ -140,7 +140,7 @@ public class ContextHandlerCollection extends Handler.Collection
if (pathBranches == null)
return null;
String path = request.getPathInContext();
String path = Request.getPathInContext(request);
if (!path.startsWith("/"))
{
super.handle(request);

View File

@ -28,16 +28,14 @@ import org.slf4j.LoggerFactory;
public class ContextRequest extends Request.WrapperProcessor implements Invocable, Supplier<Request.Processor>, Runnable
{
private static final Logger LOG = LoggerFactory.getLogger(ContextRequest.class);
private final String _pathInContext;
private final ContextHandler _contextHandler;
private final ContextHandler.Context _context;
private Response _response;
private Callback _callback;
protected ContextRequest(ContextHandler contextHandler, ContextHandler.Context context, Request wrapped, String pathInContext)
protected ContextRequest(ContextHandler contextHandler, ContextHandler.Context context, Request wrapped)
{
super(wrapped);
_pathInContext = pathInContext;
_contextHandler = contextHandler;
_context = context;
}
@ -115,11 +113,6 @@ public class ContextRequest extends Request.WrapperProcessor implements Invocabl
return _context;
}
public String getPathInContext()
{
return _pathInContext;
}
@Override
public Object getAttribute(String name)
{
@ -127,7 +120,7 @@ public class ContextRequest extends Request.WrapperProcessor implements Invocabl
return switch (name)
{
case "o.e.j.s.h.ScopedRequest.contextPath" -> _context.getContextPath();
case "o.e.j.s.h.ScopedRequest.pathInContext" -> _pathInContext;
case "o.e.j.s.h.ScopedRequest.pathInContext" -> Request.getPathInContext(this);
default -> super.getAttribute(name);
};
}

View File

@ -109,7 +109,7 @@ public class DefaultHandler extends Handler.Processor
String method = request.getMethod();
// little cheat for common request
if (isServeIcon() && _favicon != null && HttpMethod.GET.is(method) && request.getPathInContext().equals("/favicon.ico"))
if (isServeIcon() && _favicon != null && HttpMethod.GET.is(method) && Request.getPathInContext(request).equals("/favicon.ico"))
{
ByteBuffer content = BufferUtil.EMPTY_BUFFER;
if (_faviconModifiedMs > 0 && request.getHeaders().getDateField(HttpHeader.IF_MODIFIED_SINCE) == _faviconModifiedMs)
@ -127,7 +127,7 @@ public class DefaultHandler extends Handler.Processor
return;
}
if (!isShowContexts() || !HttpMethod.GET.is(method) || !request.getPathInContext().equals("/"))
if (!isShowContexts() || !HttpMethod.GET.is(method) || !Request.getPathInContext(request).equals("/"))
{
Response.writeError(request, response, callback, HttpStatus.NOT_FOUND_404, null);
return;

View File

@ -110,7 +110,7 @@ public class ErrorProcessor implements Request.Processor
try
{
generateAcceptableResponse(request, response, code, message, cause, callback);
generateResponse(request, response, code, message, cause, callback);
}
catch (Throwable x)
{
@ -120,7 +120,7 @@ public class ErrorProcessor implements Request.Processor
}
}
protected void generateAcceptableResponse(Request request, Response response, int code, String message, Throwable cause, Callback callback) throws IOException
protected void generateResponse(Request request, Response response, int code, String message, Throwable cause, Callback callback) throws IOException
{
List<String> acceptable = request.getHeaders().getQualityCSV(HttpHeader.ACCEPT, QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING);
if (acceptable.isEmpty())

View File

@ -222,7 +222,7 @@ public class InetAccessHandler extends Handler.Wrapper
protected boolean isAllowed(InetAddress addr, Request request)
{
String connectorName = request.getConnectionMetaData().getConnector().getName();
String path = request.getPathInContext();
String path = Request.getPathInContext(request);
return _set.test(new AccessTuple(connectorName, addr, path));
}

View File

@ -102,8 +102,9 @@ public class MovedContextHandler extends ContextHandler
return;
String path = _newContextURL;
if (!_discardPathInfo && request.getPathInContext() != null)
path = URIUtil.addPaths(path, request.getPathInContext());
String pathInContext = Request.getPathInContext(request);
if (!_discardPathInfo && pathInContext != null)
path = URIUtil.addPaths(path, pathInContext);
HttpURI uri = request.getHttpURI();
StringBuilder location = new StringBuilder();

View File

@ -0,0 +1,103 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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.server.handler;
import java.io.IOException;
import java.util.List;
import java.util.function.Supplier;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.component.Dumpable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Handler that delegates to other handlers through a configured {@link PathMappings}.
*/
public class PathMappingsHandler extends Handler.AbstractContainer
{
private static final Logger LOG = LoggerFactory.getLogger(PathMappingsHandler.class);
private final PathMappings<Handler> mappings = new PathMappings<>();
@Override
public void addHandler(Handler handler)
{
throw new UnsupportedOperationException("Arbitrary addHandler() not supported, use addMapping() instead");
}
@Override
public void addHandler(Supplier<Handler> supplier)
{
throw new UnsupportedOperationException("Arbitrary addHandler() not supported, use addMapping() instead");
}
@Override
public List<Handler> getHandlers()
{
return mappings.streamResources().map(MappedResource::getResource).toList();
}
public void addMapping(PathSpec pathSpec, Handler handler)
{
if (isStarted())
throw new IllegalStateException("Cannot add mapping: " + this);
// check that self isn't present
if (handler == this || handler instanceof Handler.Container container && container.getDescendants().contains(this))
throw new IllegalStateException("Unable to addHandler of self: " + handler);
// check existing mappings
for (MappedResource<Handler> entry : mappings)
{
Handler entryHandler = entry.getResource();
if (entryHandler == this ||
entryHandler == handler ||
(entryHandler instanceof Handler.Container container && container.getDescendants().contains(this)))
throw new IllegalStateException("addMapping loop detected: " + handler);
}
mappings.put(pathSpec, handler);
addBean(handler);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
Dumpable.dumpObjects(out, indent, this, mappings);
}
@Override
public Request.Processor handle(Request request) throws Exception
{
String pathInContext = Request.getPathInContext(request);
MatchedResource<Handler> matchedResource = mappings.getMatched(pathInContext);
if (matchedResource == null)
{
if (LOG.isDebugEnabled())
LOG.debug("No match on pathInContext of {}", pathInContext);
return null;
}
if (LOG.isDebugEnabled())
LOG.debug("Matched pathInContext of {} to {} -> {}", pathInContext, matchedResource.getPathSpec(), matchedResource.getResource());
return matchedResource.getResource().handle(request);
}
}

View File

@ -0,0 +1,138 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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.server.handler;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ExceptionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An ErrorProcessor that can re-handle a request at an error page location.
*/
public abstract class ReHandlingErrorProcessor extends ErrorProcessor
{
private static final Logger LOG = LoggerFactory.getLogger(ReHandlingErrorProcessor.class);
private final Handler _handler;
protected ReHandlingErrorProcessor(Handler handler)
{
_handler = handler;
}
@Override
public InvocationType getInvocationType()
{
return _handler.getInvocationType();
}
@Override
protected void generateResponse(Request request, Response response, int code, String message, Throwable cause, Callback callback) throws IOException
{
if (request.getAttribute(ReHandlingErrorProcessor.class.getName()) == null)
{
String pathInContext = getReHandlePathInContext(request, code, cause);
if (pathInContext != null)
{
request.setAttribute(ReHandlingErrorProcessor.class.getName(), pathInContext);
HttpURI uri = Request.newHttpURIFrom(request, pathInContext);
Request.Wrapper wrapper = new ReHandleRequestWrapper(request, uri);
try
{
Request.Processor processor = _handler.handle(wrapper);
if (processor != null)
{
response.setStatus(200);
processor.process(wrapper, response, callback);
return;
}
}
catch (Exception e)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to process error {}", wrapper, e);
if (cause != null && ExceptionUtil.areNotAssociated(cause, e))
cause.addSuppressed(e);
response.setStatus(code);
}
}
}
super.generateResponse(request, response, code, message, cause, callback);
}
protected abstract String getReHandlePathInContext(Request request, int code, Throwable cause);
/**
* An ErrorPageErrorProcessor that uses a map of error codes to select a page.
*/
public static class ByHttpStatus extends ReHandlingErrorProcessor
{
private final Map<Integer, String> _statusMap = new ConcurrentHashMap<>();
public ByHttpStatus(Handler handler)
{
super(handler);
}
@Override
protected String getReHandlePathInContext(Request request, int code, Throwable cause)
{
return get(code);
}
public String put(int code, String pathInContext)
{
return _statusMap.put(code, pathInContext);
}
public String get(int code)
{
return _statusMap.get(code);
}
public String remove(int code)
{
return _statusMap.remove(code);
}
}
private static class ReHandleRequestWrapper extends Request.Wrapper
{
private final HttpURI _uri;
public ReHandleRequestWrapper(Request request, HttpURI uri)
{
super(request);
_uri = uri;
}
@Override
public HttpURI getHttpURI()
{
return _uri;
}
}
}

View File

@ -101,8 +101,9 @@ public class ResourceHandler extends Handler.Wrapper
for (String welcome : _welcomes)
{
String welcomeInContext = URIUtil.addPaths(request.getPathInContext(), welcome);
Resource welcomePath = _resourceBase.resolve(request.getPathInContext()).resolve(welcome);
String pathInContext = Request.getPathInContext(request);
String welcomeInContext = URIUtil.addPaths(pathInContext, welcome);
Resource welcomePath = _resourceBase.resolve(pathInContext).resolve(welcome);
if (Resources.isReadableFile(welcomePath))
return welcomeInContext;
}
@ -120,7 +121,7 @@ public class ResourceHandler extends Handler.Wrapper
return super.handle(request);
}
HttpContent content = _resourceService.getContent(request.getPathInContext(), request);
HttpContent content = _resourceService.getContent(Request.getPathInContext(request), request);
if (content == null)
return super.handle(request); // no content - try other handlers

View File

@ -15,35 +15,111 @@ package org.eclipse.jetty.server.handler;
import java.util.List;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.URIUtil;
/**
* <p>Inspired by nginx's {@code try_files} functionality.</p>
* <p> This handler can be configured with a list of URI paths.
* The special token {@code $path} represents the current request URI
* path (the portion after the context path).</p>
*
* <p>This handler can be configured with a list of rewrite URI paths.
* The special token {@code $path} represents the current request
* {@code pathInContext} (the portion after the context path).</p>
*
* <p>Typical example of how this handler can be configured is the following:</p>
* <pre>{@code
* TryPathsHandler tryPaths = new TryPathsHandler();
* tryPaths.setPaths("/maintenance.html", "$path", "/index.php?p=$path");
* TryPathsHandler tryPathsHandler = new TryPathsHandler();
* tryPathsHandler.setPaths("/maintenance.html", "$path", "/index.php?p=$path");
*
* PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
* tryPathsHandler.setHandler(pathMappingsHandler);
*
* pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new PHPHandler());
* pathMappingsHandler.addMapping(new ServletPathSpec("/"), new ResourceHandler());
* }</pre>
* <p>For a request such as {@code /context/path/to/resource.ext}, this
* handler will try to serve the {@code /maintenance.html} file if it finds
* it; failing that, it will try to serve the {@code /path/to/resource.ext}
* file if it finds it; failing that it will forward the request to
* {@code /index.php?p=/path/to/resource.ext} to the next handler.</p>
* <p>The last URI path specified in the list is therefore the "fallback" to
* which the request is forwarded to in case no previous files can be found.</p>
* <p>The file paths are resolved against {@link Context#getBaseResource()}
* to make sure that only files visible to the application are served.</p>
*
* <p>For a request such as {@code /context/path/to/resource.ext}:</p>
* <ul>
* <li>This handler rewrites the request {@code pathInContext} to
* {@code /maintenance.html} and forwards the request to the next handler,
* where it matches the {@code /} mapping, hitting the {@code ResourceHandler}
* that serves the file if it exists.</li>
* <li>Otherwise, this handler rewrites the request {@code pathInContext} to
* {@code /path/to/resource.ext} and forwards the request to the next handler,
* where it matches the {@code /} mapping, hitting the {@code ResourceHandler}
* that serves the file if it exists.</li>
* <li>Otherwise, this handler rewrites the request {@code pathInContext} to
* {@code /index.php?p=/path/to/resource.ext} and forwards the request to
* the next handler, where it matches the {@code *.php} mapping, hitting
* the {@code PHPHandler}.</li>
* </ul>
*
* <p>The original path and query may be stored as request attributes,
* under the names specified by {@link #setOriginalPathAttribute(String)}
* and {@link #setOriginalQueryAttribute(String)}.</p>
*/
public class TryPathsHandler extends Handler.Wrapper
{
private String originalPathAttribute;
private String originalQueryAttribute;
private List<String> paths;
/**
* @return the attribute name of the original request path
*/
public String getOriginalPathAttribute()
{
return originalPathAttribute;
}
/**
* <p>Sets the request attribute name to use to
* retrieve the original request path.</p>
*
* @param originalPathAttribute the attribute name of the original
* request path
*/
public void setOriginalPathAttribute(String originalPathAttribute)
{
this.originalPathAttribute = originalPathAttribute;
}
/**
* @return the attribute name of the original request query
*/
public String getOriginalQueryAttribute()
{
return originalQueryAttribute;
}
/**
* <p>Sets the request attribute name to use to
* retrieve the original request query.</p>
*
* @param originalQueryAttribute the attribute name of the original
* request query
*/
public void setOriginalQueryAttribute(String originalQueryAttribute)
{
this.originalQueryAttribute = originalQueryAttribute;
}
/**
* @return the rewrite URI paths
*/
public List<String> getPaths()
{
return paths;
}
/**
* <p>Sets a list of rewrite URI paths.</p>
* <p>The special token {@code $path} represents the current request
* {@code pathInContext} (the portion after the context path).</p>
*
* @param paths the rewrite URI paths
*/
public void setPaths(List<String> paths)
{
this.paths = paths;
@ -52,49 +128,60 @@ public class TryPathsHandler extends Handler.Wrapper
@Override
public Request.Processor handle(Request request) throws Exception
{
String interpolated = interpolate(request, "$path");
Resource rootResource = request.getContext().getBaseResource();
if (rootResource != null)
for (String path : paths)
{
for (String path : paths)
{
interpolated = interpolate(request, path);
Resource resource = rootResource.resolve(interpolated);
if (resource != null && resource.exists())
break;
}
String interpolated = interpolate(request, path);
Request.WrapperProcessor result = new Request.WrapperProcessor(new TryPathsRequest(request, interpolated));
Request.Processor childProcessor = super.handle(result);
if (childProcessor != null)
return result.wrapProcessor(childProcessor);
}
Request.WrapperProcessor result = new Request.WrapperProcessor(new TryPathsRequest(request, interpolated));
return result.wrapProcessor(super.handle(result));
}
private Request.Processor fallback(Request request) throws Exception
{
String fallback = paths.isEmpty() ? "$path" : paths.get(paths.size() - 1);
String interpolated = interpolate(request, fallback);
return super.handle(new TryPathsRequest(request, interpolated));
return null;
}
private String interpolate(Request request, String value)
{
String path = request.getPathInContext();
String path = Request.getPathInContext(request);
return value.replace("$path", path);
}
private static class TryPathsRequest extends Request.Wrapper
private class TryPathsRequest extends Request.Wrapper
{
private final String pathInContext;
private final HttpURI _uri;
public TryPathsRequest(Request wrapped, String pathInContext)
public TryPathsRequest(Request wrapped, String newPathQuery)
{
super(wrapped);
this.pathInContext = pathInContext;
HttpURI originalURI = wrapped.getHttpURI();
String originalPathAttribute = getOriginalPathAttribute();
if (originalPathAttribute != null)
setAttribute(originalPathAttribute, Request.getPathInContext(wrapped));
String originalQueryAttribute = getOriginalQueryAttribute();
if (originalQueryAttribute != null)
setAttribute(originalQueryAttribute, originalURI.getQuery());
String originalContextPath = Request.getContextPath(wrapped);
HttpURI.Mutable rewrittenURI = HttpURI.build(originalURI);
int queryIdx = newPathQuery.indexOf('?');
if (queryIdx >= 0)
{
String path = newPathQuery.substring(0, queryIdx);
rewrittenURI.path(URIUtil.addPaths(originalContextPath, path));
rewrittenURI.query(newPathQuery.substring(queryIdx + 1));
}
else
{
rewrittenURI.path(URIUtil.addPaths(originalContextPath, newPathQuery));
}
_uri = rewrittenURI.asImmutable();
}
@Override
public String getPathInContext()
public HttpURI getHttpURI()
{
return pathInContext;
return _uri;
}
}
}

View File

@ -523,7 +523,7 @@ public class GzipHandler extends Handler.Wrapper implements GzipFactory
@Override
public Request.Processor handle(Request request) throws Exception
{
final String path = request.getPathInContext();
final String path = Request.getPathInContext(request);
if (LOG.isDebugEnabled())
LOG.debug("{} handle {}", this, request);

View File

@ -605,7 +605,7 @@ public class HttpChannelState implements HttpChannel, Components
{
if (!HttpMethod.PRI.is(request.getMethod()) &&
!HttpMethod.CONNECT.is(request.getMethod()) &&
!_request.getPathInContext().startsWith("/") &&
!Request.getPathInContext(_request).startsWith("/") &&
!HttpMethod.OPTIONS.is(request.getMethod()))
{
_processState = ProcessState.PROCESSING;
@ -796,7 +796,7 @@ public class HttpChannelState implements HttpChannel, Components
return _loggedRequest == null ? this : _loggedRequest;
}
HttpStream getStream()
HttpStream getHttpStream()
{
return getHttpChannel()._stream;
}
@ -903,12 +903,6 @@ public class HttpChannelState implements HttpChannel, Components
return getConnectionMetaData().getConnector().getServer().getContext();
}
@Override
public String getPathInContext()
{
return _metaData.getURI().getCanonicalPath();
}
@Override
public HttpFields getHeaders()
{
@ -1013,7 +1007,7 @@ public class HttpChannelState implements HttpChannel, Components
@Override
public void push(MetaData.Request request)
{
getStream().push(request);
getHttpStream().push(request);
}
@Override
@ -1047,7 +1041,7 @@ public class HttpChannelState implements HttpChannel, Components
@Override
public TunnelSupport getTunnelSupport()
{
return getStream().getTunnelSupport();
return getHttpStream().getTunnelSupport();
}
@Override
@ -1468,7 +1462,7 @@ public class HttpChannelState implements HttpChannel, Components
public InvocationType getInvocationType()
{
// TODO review this as it is probably not correct
return _request.getStream().getInvocationType();
return _request.getHttpStream().getInvocationType();
}
}

View File

@ -614,7 +614,7 @@ public class CustomRequestLogTest
@Override
public void process(Request request, Response response, Callback callback)
{
if (request.getPathInContext().equals("/abort"))
if (Request.getPathInContext(request).equals("/abort"))
{
Callback cbk = Callback.from(() -> callback.failed(new QuietException.Exception("test fail")), callback::failed);
Content.Sink.write(response, false, "data", cbk);

View File

@ -25,11 +25,13 @@ import javax.xml.parsers.DocumentBuilderFactory;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.ErrorProcessor;
import org.eclipse.jetty.server.handler.ReHandlingErrorProcessor;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@ -71,21 +73,22 @@ public class ErrorProcessorTest
@Override
public void process(Request request, Response response, Callback callback)
{
if (request.getPathInContext().startsWith("/badmessage/"))
String pathInContext = Request.getPathInContext(request);
if (pathInContext.startsWith("/badmessage/"))
{
int code = Integer.parseInt(request.getPathInContext().substring(request.getPathInContext().lastIndexOf('/') + 1));
int code = Integer.parseInt(pathInContext.substring(pathInContext.lastIndexOf('/') + 1));
throw new BadMessageException(code);
}
// produce an exception with an JSON formatted cause message
if (request.getPathInContext().startsWith("/jsonmessage/"))
if (pathInContext.startsWith("/jsonmessage/"))
{
String message = "\"}, \"glossary\": {\n \"title\": \"example\"\n }\n {\"";
throw new TestException(message);
}
// produce an exception with an XML cause message
if (request.getPathInContext().startsWith("/xmlmessage/"))
if (pathInContext.startsWith("/xmlmessage/"))
{
String message =
"<!DOCTYPE glossary PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\">\n" +
@ -96,14 +99,14 @@ public class ErrorProcessorTest
}
// produce an exception with an HTML cause message
if (request.getPathInContext().startsWith("/htmlmessage/"))
if (pathInContext.startsWith("/htmlmessage/"))
{
String message = "<hr/><script>alert(42)</script>%3Cscript%3E";
throw new TestException(message);
}
// produce an exception with a UTF-8 cause message
if (request.getPathInContext().startsWith("/utf8message/"))
if (pathInContext.startsWith("/utf8message/"))
{
// @checkstyle-disable-check : AvoidEscapedUnicodeCharacters
String message = "Euro is &euro; and \u20AC and %E2%82%AC";
@ -111,6 +114,17 @@ public class ErrorProcessorTest
throw new TestException(message);
}
// 200 response
if (pathInContext.startsWith("/ok/"))
{
Content.Sink.write(
response,
true,
"%s Error %s : %s%n".formatted(pathInContext, request.getAttribute(ErrorProcessor.ERROR_STATUS), request.getAttribute(ErrorProcessor.ERROR_MESSAGE)),
callback);
return;
}
Response.writeError(request, response, callback, 404);
}
});
@ -689,6 +703,104 @@ public class ErrorProcessorTest
assertThat(response, containsString("Server Error"));
}
@Test
public void testRootReHandlingErrorProcessor() throws Exception
{
ReHandlingErrorProcessor.ByHttpStatus errorProcessor = new ReHandlingErrorProcessor.ByHttpStatus(server);
errorProcessor.put(400, "/ok/badMessage");
server.setErrorProcessor(errorProcessor);
String rawResponse = connector.getResponse("""
GET /no/host HTTP/1.1
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(200));
assertThat(response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
assertThat(response.getContent(), containsString("/ok/badMessage Error 400 : No Host"));
}
@Test
public void testRootReHandlingErrorProcessorLoop() throws Exception
{
ReHandlingErrorProcessor.ByHttpStatus errorProcessor = new ReHandlingErrorProcessor.ByHttpStatus(server);
errorProcessor.put(404, "/not/found");
server.setErrorProcessor(errorProcessor);
String rawResponse = connector.getResponse("""
GET /not/found HTTP/1.1
Host: localhost
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(404));
assertThat(response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
assertThat(response.getContent(), containsString("<title>Error 404 Not Found</title>"));
}
@Test
public void testRootReHandlingErrorProcessorExceptionLoop() throws Exception
{
ReHandlingErrorProcessor.ByHttpStatus errorProcessor = new ReHandlingErrorProcessor.ByHttpStatus(server);
errorProcessor.put(444, "/badmessage/444");
server.setErrorProcessor(errorProcessor);
String rawResponse = connector.getResponse("""
GET /badmessage/444 HTTP/1.1
Host: localhost
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(444));
assertThat(response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
assertThat(response.getContent(), containsString("<title>Error 444</title>"));
}
@Test
public void testContextReHandlingErrorProcessor() throws Exception
{
server.stop();
ContextHandler context = new ContextHandler("/ctx");
context.setHandler(server.getHandler());
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.addHandler(context);
server.setHandler(contexts);
server.setErrorProcessor(new ErrorProcessor()
{
@Override
public void process(Request request, Response response, Callback callback)
{
throw new UnsupportedOperationException();
}
});
server.start();
ReHandlingErrorProcessor.ByHttpStatus errorProcessor = new ReHandlingErrorProcessor.ByHttpStatus(context);
errorProcessor.put(444, "/ok/badMessage");
context.setErrorProcessor(errorProcessor);
String rawResponse = connector.getResponse("""
GET /ctx/badmessage/444 HTTP/1.1
Host: localhost
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(200));
assertThat(response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
assertThat(response.getContent(), containsString("/ok/badMessage Error 444"));
}
static class TestException extends RuntimeException implements QuietException
{
public TestException(String message)

View File

@ -654,7 +654,7 @@ public class HttpConfigurationAuthorityOverrideTest
@Override
public Request.Processor handle(Request request) throws Exception
{
if (!request.getPathInContext().startsWith("/dump"))
if (!Request.getPathInContext(request).startsWith("/dump"))
return null;
return (rq, rs, cb) ->
{
@ -679,7 +679,7 @@ public class HttpConfigurationAuthorityOverrideTest
@Override
public Request.Processor handle(Request request) throws Exception
{
if (!request.getPathInContext().startsWith("/redirect"))
if (!Request.getPathInContext(request).startsWith("/redirect"))
return null;
return (rq, rs, cb) ->
@ -696,7 +696,7 @@ public class HttpConfigurationAuthorityOverrideTest
@Override
public Request.Processor handle(Request request) throws Exception
{
if (!request.getPathInContext().startsWith("/error"))
if (!Request.getPathInContext(request).startsWith("/error"))
return null;
return super.handle(request);
}

View File

@ -84,7 +84,7 @@ public class SlowClientWithPipelinedRequestTest
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
if ("/content".equals(request.getPathInContext()))
if ("/content".equals(Request.getPathInContext(request)))
{
// TODO is this still a valid test?
// We simulate what the DefaultServlet does, bypassing the blocking

View File

@ -0,0 +1,102 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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.server.handler;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ContextHandlerDeepTest
{
private Server server;
private LocalConnector connector;
private void startServer(Handler handler) throws Exception
{
server = new Server();
connector = new LocalConnector(server);
server.addConnector(connector);
server.setHandler(handler);
server.start();
}
@AfterEach
public void stopServer()
{
LifeCycle.stop(server);
}
@Test
public void testNestedThreeDeepContextHandler() throws Exception
{
ContextHandler contextHandlerA = new ContextHandler();
contextHandlerA.setContextPath("/a");
ContextHandler contextHandlerB = new ContextHandler();
contextHandlerB.setContextPath("/a/b");
ContextHandler contextHandlerC = new ContextHandler();
contextHandlerC.setContextPath("/a/b/c");
contextHandlerA.setHandler(contextHandlerB);
contextHandlerB.setHandler(contextHandlerC);
contextHandlerC.setHandler(new Handler.Processor()
{
@Override
public void process(Request request, Response response, Callback callback)
{
response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8");
String msg = """
contextPath=%s
pathInContext=%s
httpURI.getPath=%s
"""
.formatted(
Request.getContextPath(request),
Request.getPathInContext(request),
request.getHttpURI().getPath()
);
response.write(true, BufferUtil.toBuffer(msg), callback);
}
});
startServer(contextHandlerA);
String rawRequest = """
GET /a/b/c/d HTTP/1.1\r
Host: local\r
Connection: close\r
""";
HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(rawRequest));
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContent(), containsString("contextPath=/a/b/c\n"));
assertThat(response.getContent(), containsString("pathInContext=/d\n"));
assertThat(response.getContent(), containsString("httpURI.getPath=/a/b/c/d\n"));
}
}

View File

@ -60,6 +60,7 @@ import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ContextHandlerTest
@ -188,7 +189,7 @@ public class ContextHandlerTest
{
if (request != null)
{
assertThat(request.getPathInContext(), equalTo("/path"));
assertThat(Request.getPathInContext(request), equalTo("/path"));
assertThat(request.getContext(), sameInstance(_context));
}
assertThat(ContextHandler.getCurrentContext(), sameInstance(_context));
@ -586,6 +587,31 @@ public class ContextHandlerTest
assertThat(result.get(), equalTo("OK"));
}
@Test
public void testSetHandlerLoopSelf()
{
ContextHandler contextHandlerA = new ContextHandler();
assertThrows(IllegalStateException.class, () -> contextHandlerA.setHandler(contextHandlerA));
}
@Test
public void testSetHandlerLoopDeepWrapper()
{
ContextHandler contextHandlerA = new ContextHandler();
Handler.Wrapper handlerWrapper = new Handler.Wrapper();
contextHandlerA.setHandler(handlerWrapper);
assertThrows(IllegalStateException.class, () -> handlerWrapper.setHandler(contextHandlerA));
}
@Test
public void testAddHandlerLoopDeep()
{
ContextHandler contextHandlerA = new ContextHandler();
Handler.Collection handlerCollection = new Handler.Collection();
contextHandlerA.setHandler(handlerCollection);
assertThrows(IllegalStateException.class, () -> handlerCollection.addHandler(contextHandlerA));
}
private static class ScopeListener implements ContextHandler.ContextScopeListener
{
private static final Request NULL = new Request.Wrapper(null);

View File

@ -150,7 +150,7 @@ public class DumpHandler extends Handler.Processor.Blocking
writer.write("<pre>httpURI.path=" + httpURI.getPath() + "</pre><br/>\n");
writer.write("<pre>httpURI.query=" + httpURI.getQuery() + "</pre><br/>\n");
writer.write("<pre>httpURI.pathQuery=" + httpURI.getPathQuery() + "</pre><br/>\n");
writer.write("<pre>pathInContext=" + request.getPathInContext() + "</pre><br/>\n");
writer.write("<pre>pathInContext=" + Request.getPathInContext(request) + "</pre><br/>\n");
writer.write("<pre>contentType=" + request.getHeaders().get(HttpHeader.CONTENT_TYPE) + "</pre><br/>\n");
writer.write("<pre>servername=" + Request.getServerName(request) + "</pre><br/>\n");
writer.write("<pre>local=" + Request.getLocalAddr(request) + ":" + Request.getLocalPort(request) + "</pre><br/>\n");

View File

@ -0,0 +1,283 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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.server.handler;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class PathMappingsHandlerTest
{
private Server server;
private LocalConnector connector;
public void startServer(Handler handler) throws Exception
{
server = new Server();
connector = new LocalConnector(server);
server.addConnector(connector);
server.addHandler(handler);
server.start();
}
@AfterEach
public void stopServer()
{
LifeCycle.stop(server);
}
public HttpTester.Response executeRequest(String rawRequest) throws Exception
{
String rawResponse = connector.getResponse(rawRequest);
return HttpTester.parseResponse(rawResponse);
}
/**
* Test where there are no mappings, and no wrapper.
*/
@Test
public void testEmpty() throws Exception
{
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/");
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
contextHandler.setHandler(pathMappingsHandler);
startServer(contextHandler);
HttpTester.Response response = executeRequest("""
GET / HTTP/1.1\r
Host: local\r
Connection: close\r
""");
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
}
/**
* Test where there is only a single mapping, and no wrapper.
*/
@Test
public void testOnlyMappingSuffix() throws Exception
{
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/");
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new SimpleHandler("PhpExample Hit"));
contextHandler.setHandler(pathMappingsHandler);
startServer(contextHandler);
HttpTester.Response response = executeRequest("""
GET /hello HTTP/1.1\r
Host: local\r
Connection: close\r
""");
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
response = executeRequest("""
GET /hello.php HTTP/1.1\r
Host: local\r
Connection: close\r
""");
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("PhpExample Hit", response.getContent());
}
public static Stream<Arguments> severalMappingsInput()
{
return Stream.of(
Arguments.of("/hello", HttpStatus.OK_200, "FakeResourceHandler Hit"),
Arguments.of("/index.html", HttpStatus.OK_200, "FakeSpecificStaticHandler Hit"),
Arguments.of("/index.php", HttpStatus.OK_200, "PhpHandler Hit"),
Arguments.of("/config.php", HttpStatus.OK_200, "PhpHandler Hit"),
Arguments.of("/css/main.css", HttpStatus.OK_200, "FakeResourceHandler Hit")
);
}
/**
* Test where there are a few mappings, with a root mapping, and no wrapper.
* This means the wrapper would not ever be hit, as all inputs would match at
* least 1 mapping.
*/
@ParameterizedTest
@MethodSource("severalMappingsInput")
public void testSeveralMappingAndNoWrapper(String requestPath, int expectedStatus, String expectedResponseBody) throws Exception
{
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/");
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
pathMappingsHandler.addMapping(new ServletPathSpec("/"), new SimpleHandler("FakeResourceHandler Hit"));
pathMappingsHandler.addMapping(new ServletPathSpec("/index.html"), new SimpleHandler("FakeSpecificStaticHandler Hit"));
pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new SimpleHandler("PhpHandler Hit"));
contextHandler.setHandler(pathMappingsHandler);
startServer(contextHandler);
HttpTester.Response response = executeRequest("""
GET %s HTTP/1.1\r
Host: local\r
Connection: close\r
""".formatted(requestPath));
assertEquals(expectedStatus, response.getStatus());
assertEquals(expectedResponseBody, response.getContent());
}
@Test
public void testDump() throws Exception
{
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/");
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
pathMappingsHandler.addMapping(new ServletPathSpec("/"), new SimpleHandler("FakeResourceHandler Hit"));
pathMappingsHandler.addMapping(new ServletPathSpec("/index.html"), new SimpleHandler("FakeSpecificStaticHandler Hit"));
pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new SimpleHandler("PhpHandler Hit"));
contextHandler.setHandler(pathMappingsHandler);
startServer(contextHandler);
String dump = contextHandler.dump();
assertThat(dump, containsString("FakeResourceHandler"));
assertThat(dump, containsString("FakeSpecificStaticHandler"));
assertThat(dump, containsString("PhpHandler"));
assertThat(dump, containsString("PathMappings[size=3]"));
}
@Test
public void testGetDescendantsSimple()
{
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/");
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
pathMappingsHandler.addMapping(new ServletPathSpec("/"), new SimpleHandler("default"));
pathMappingsHandler.addMapping(new ServletPathSpec("/index.html"), new SimpleHandler("specific"));
pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new SimpleHandler("php"));
List<String> actualHandlers = pathMappingsHandler.getDescendants().stream().map(Objects::toString).toList();
String[] expectedHandlers = {
"SimpleHandler[msg=\"default\"]",
"SimpleHandler[msg=\"specific\"]",
"SimpleHandler[msg=\"php\"]"
};
assertThat(actualHandlers, containsInAnyOrder(expectedHandlers));
}
@Test
public void testGetDescendantsDeep()
{
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/");
Handler.Collection handlerCollection = new Handler.Collection();
handlerCollection.addHandler(new SimpleHandler("phpIndex"));
Handler.Wrapper handlerWrapper = new Handler.Wrapper(new SimpleHandler("other"));
handlerCollection.addHandler(handlerWrapper);
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
pathMappingsHandler.addMapping(new ServletPathSpec("/"), new SimpleHandler("default"));
pathMappingsHandler.addMapping(new ServletPathSpec("/index.html"), new SimpleHandler("specific"));
pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), handlerCollection);
List<String> actualHandlers = pathMappingsHandler.getDescendants().stream().map(Objects::toString).toList();
String[] expectedHandlers = {
"SimpleHandler[msg=\"default\"]",
"SimpleHandler[msg=\"specific\"]",
handlerCollection.toString(),
handlerWrapper.toString(),
"SimpleHandler[msg=\"phpIndex\"]",
"SimpleHandler[msg=\"other\"]"
};
assertThat(actualHandlers, containsInAnyOrder(expectedHandlers));
}
@Test
public void testAddLoopSelf()
{
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
assertThrows(IllegalStateException.class, () -> pathMappingsHandler.addMapping(new ServletPathSpec("/self"), pathMappingsHandler));
}
@Test
public void testAddLoopContext()
{
ContextHandler contextHandler = new ContextHandler();
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
contextHandler.setHandler(pathMappingsHandler);
assertThrows(IllegalStateException.class, () -> pathMappingsHandler.addMapping(new ServletPathSpec("/loop"), contextHandler));
}
private static class SimpleHandler extends Handler.Processor
{
private final String message;
public SimpleHandler(String message)
{
this.message = message;
}
@Override
public void process(Request request, Response response, Callback callback)
{
assertTrue(isStarted());
response.setStatus(HttpStatus.OK_200);
response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8");
response.write(true, BufferUtil.toBuffer(message, StandardCharsets.UTF_8), callback);
}
@Override
public String toString()
{
return String.format("%s[msg=\"%s\"]", SimpleHandler.class.getSimpleName(), message);
}
}
}

View File

@ -199,7 +199,7 @@ public class ThreadLimitHandlerTest
public void process(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(HttpStatus.OK_200);
if (!"/other".equals(request.getPathInContext()))
if (!"/other".equals(Request.getPathInContext(request)))
{
try
{

View File

@ -22,16 +22,18 @@ import java.nio.file.StandardOpenOption;
import java.util.List;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
@ -41,18 +43,20 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TryPathsHandlerTest
{
public WorkDir workDir;
private static final String CONTEXT_PATH = "/ctx";
private Server server;
private SslContextFactory.Server sslContextFactory;
private ServerConnector connector;
private ServerConnector sslConnector;
private Path rootPath;
private String contextPath;
private void start(List<String> paths, Handler handler) throws Exception
{
@ -66,22 +70,18 @@ public class TryPathsHandlerTest
sslConnector = new ServerConnector(server, 1, 1, sslContextFactory);
server.addConnector(sslConnector);
contextPath = "/ctx";
ContextHandler context = new ContextHandler(contextPath);
rootPath = Files.createDirectories(MavenTestingUtils.getTargetTestingPath(getClass().getSimpleName()));
FS.cleanDirectory(rootPath);
ContextHandler context = new ContextHandler(CONTEXT_PATH);
rootPath = workDir.getEmptyPathDir();
context.setBaseResourceAsPath(rootPath);
server.setHandler(context);
TryPathsHandler tryPaths = new TryPathsHandler();
context.setHandler(tryPaths);
tryPaths.setPaths(paths);
tryPaths.setHandler(handler);
ResourceHandler resourceHandler = new ResourceHandler();
tryPaths.setHandler(resourceHandler);
resourceHandler.setHandler(handler);
server.setDumpAfterStart(true);
server.start();
}
@ -94,47 +94,155 @@ public class TryPathsHandlerTest
@Test
public void testTryPaths() throws Exception
{
start(List.of("/maintenance.txt", "$path", "/forward?p=$path"), new Handler.Processor()
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setDirAllowed(false);
resourceHandler.setHandler(new Handler.Abstract()
{
@Override
public void process(Request request, Response response, Callback callback)
public Request.Processor handle(Request request)
{
assertThat(request.getPathInContext(), equalTo("/forward?p=/last"));
response.setStatus(HttpStatus.NO_CONTENT_204);
callback.succeeded();
if (!Request.getPathInContext(request).startsWith("/forward"))
return null;
return new Handler.Processor()
{
public void process(Request request, Response response, Callback callback)
{
assertThat(Request.getPathInContext(request), equalTo("/forward"));
assertThat(request.getHttpURI().getQuery(), equalTo("p=/last"));
response.setStatus(HttpStatus.NO_CONTENT_204);
callback.succeeded();
}
};
}
});
start(List.of("/maintenance.txt", "$path", "/forward?p=$path"), resourceHandler);
try (SocketChannel channel = SocketChannel.open())
{
channel.connect(new InetSocketAddress("localhost", connector.getLocalPort()));
// Make a first request without existing file paths.
HttpTester.Request request = HttpTester.newRequest();
request.setURI(contextPath + "/last");
request.setURI(CONTEXT_PATH + "/last");
channel.write(request.generate());
HttpTester.Response response = HttpTester.parseResponse(channel);
assertNotNull(response);
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
// Create the specific file that is requested.
// Create the specific static file that is requested.
String path = "idx.txt";
Files.writeString(rootPath.resolve(path), "hello", StandardOpenOption.CREATE);
// Make a second request with the specific file.
request = HttpTester.newRequest();
request.setURI(contextPath + "/" + path);
request.setURI(CONTEXT_PATH + "/" + path);
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("hello", response.getContent());
// Create the "maintenance" file, it should be served first.
path = "maintenance.txt";
Files.writeString(rootPath.resolve(path), "maintenance", StandardOpenOption.CREATE);
// Make a third request with any path, we should get the maintenance file.
request = HttpTester.newRequest();
request.setURI(CONTEXT_PATH + "/whatever");
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("maintenance", response.getContent());
}
}
@Test
public void testTryPathsWithPathMappings() throws Exception
{
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setDirAllowed(false);
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
pathMappingsHandler.addMapping(new ServletPathSpec("/"), resourceHandler);
pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new Handler.Abstract()
{
@Override
public Request.Processor handle(Request request)
{
return new Processor()
{
@Override
public void process(Request request, Response response, Callback callback)
{
response.setStatus(HttpStatus.OK_200);
response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8");
String message = "PHP: pathInContext=%s, query=%s".formatted(Request.getPathInContext(request), request.getHttpURI().getQuery());
Content.Sink.write(response, true, message, callback);
}
};
}
});
pathMappingsHandler.addMapping(new ServletPathSpec("/forward"), new Handler.Abstract()
{
@Override
public Request.Processor handle(Request request)
{
return new Handler.Processor()
{
public void process(Request request, Response response, Callback callback)
{
assertThat(Request.getPathInContext(request), equalTo("/forward"));
assertThat(request.getHttpURI().getQuery(), equalTo("p=/last"));
response.setStatus(HttpStatus.NO_CONTENT_204);
callback.succeeded();
}
};
}
});
start(List.of("/maintenance.txt", "$path", "/forward?p=$path"), pathMappingsHandler);
try (SocketChannel channel = SocketChannel.open())
{
channel.connect(new InetSocketAddress("localhost", connector.getLocalPort()));
// Make a first request without existing file paths.
HttpTester.Request request = HttpTester.newRequest();
request.setURI(CONTEXT_PATH + "/last");
channel.write(request.generate());
HttpTester.Response response = HttpTester.parseResponse(channel);
assertNotNull(response);
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
// Create the specific static file that is requested.
String path = "idx.txt";
Files.writeString(rootPath.resolve(path), "hello", StandardOpenOption.CREATE);
// Make a second request with the specific file.
request = HttpTester.newRequest();
request.setURI(CONTEXT_PATH + "/" + path);
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("hello", response.getContent());
// Request an existing PHP file.
Files.writeString(rootPath.resolve("index.php"), "raw-php-contents", StandardOpenOption.CREATE);
request = HttpTester.newRequest();
request.setURI(CONTEXT_PATH + "/index.php");
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContent(), startsWith("PHP: pathInContext=/index.php"));
// Create the "maintenance" file, it should be served first.
path = "maintenance.txt";
Files.writeString(rootPath.resolve(path), "maintenance", StandardOpenOption.CREATE);
// Make a second request with any path, we should get the maintenance file.
request = HttpTester.newRequest();
request.setURI(contextPath + "/whatever");
request.setURI(CONTEXT_PATH + "/whatever");
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
@ -155,7 +263,7 @@ public class TryPathsHandlerTest
HttpURI httpURI = request.getHttpURI();
assertEquals("https", httpURI.getScheme());
assertTrue(request.isSecure());
assertEquals(path, request.getPathInContext());
assertEquals(path, Request.getPathInContext(request));
callback.succeeded();
}
});
@ -165,7 +273,7 @@ public class TryPathsHandlerTest
sslSocket.connect(new InetSocketAddress("localhost", sslConnector.getLocalPort()));
HttpTester.Request request = HttpTester.newRequest();
request.setURI(contextPath + path);
request.setURI(CONTEXT_PATH + path);
OutputStream output = sslSocket.getOutputStream();
output.write(BufferUtil.toArray(request.generate()));
output.flush();

View File

@ -148,7 +148,7 @@ public class GzipHandlerTest
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
String pathInfo = request.getPathInContext();
String pathInfo = Request.getPathInContext(request);
response.getHeaders().put(HttpHeader.CONTENT_TYPE, getContentTypeFromRequest(pathInfo, request));
Content.Sink.write(response, true, "This is content for " + pathInfo + "\n", callback);
}

View File

@ -139,7 +139,7 @@ public class SniSslConnectionFactoryTest
public void process(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(200);
response.getHeaders().put("X-URL", request.getPathInContext());
response.getHeaders().put("X-URL", Request.getPathInContext(request));
response.getHeaders().put("X-HOST", Request.getServerName(request));
callback.succeeded();
}

View File

@ -62,7 +62,7 @@ public class SimpleSessionHandlerTest
@Override
public void process(Request request, Response response, Callback callback)
{
String pathInContext = request.getPathInContext();
String pathInContext = Request.getPathInContext(request);
String[] split = pathInContext.substring(1).split("/");
SessionRequest sessionRequest = Request.as(request, SessionRequest.class);

View File

@ -96,12 +96,6 @@ public class TestableRequest implements Request
return null;
}
@Override
public String getPathInContext()
{
return null;
}
@Override
public HttpFields getHeaders()
{

View File

@ -261,7 +261,7 @@ public class ForwardProxyWithDynamicTransportTest
int proxyPort = proxySecure ? proxyTLSConnector.getLocalPort() : proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy proxy = new HttpProxy(proxyAddress, proxySecure, proxyProtocol);
client.getProxyConfiguration().getProxies().add(proxy);
client.getProxyConfiguration().addProxy(proxy);
String scheme = serverSecure ? "https" : "http";
int serverPort = serverSecure ? serverTLSConnector.getLocalPort() : serverConnector.getLocalPort();
@ -298,7 +298,7 @@ public class ForwardProxyWithDynamicTransportTest
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy proxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(proxy);
client.getProxyConfiguration().addProxy(proxy);
long idleTimeout = 1000;
http2Client.setStreamIdleTimeout(idleTimeout);
@ -339,7 +339,7 @@ public class ForwardProxyWithDynamicTransportTest
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(httpProxy);
client.getProxyConfiguration().addProxy(httpProxy);
proxy.stop();
CountDownLatch latch = new CountDownLatch(1);
@ -376,7 +376,7 @@ public class ForwardProxyWithDynamicTransportTest
int proxyPort = proxyConnector.getLocalPort();
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
client.getProxyConfiguration().getProxies().add(httpProxy);
client.getProxyConfiguration().addProxy(httpProxy);
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())

View File

@ -42,7 +42,7 @@ public class HttpClientIdleTimeoutTest extends AbstractTest
public void process(Request request, Response response, Callback callback) throws Exception
{
// Do not succeed the callback if it's a timeout request.
if (!request.getPathInContext().equals("/timeout"))
if (!Request.getPathInContext(request).equals("/timeout"))
callback.succeeded();
}
});
@ -76,7 +76,7 @@ public class HttpClientIdleTimeoutTest extends AbstractTest
public void process(Request request, Response response, Callback callback) throws Exception
{
// Do not succeed the callback if it's a timeout request.
if (!request.getPathInContext().equals("/timeout"))
if (!Request.getPathInContext(request).equals("/timeout"))
callback.succeeded();
}
});

View File

@ -1069,7 +1069,7 @@ public class HttpClientStreamTest extends AbstractTest
@Override
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
if (request.getPathInContext().startsWith("/303"))
if (Request.getPathInContext(request).startsWith("/303"))
org.eclipse.jetty.server.Response.sendRedirect(request, response, callback, "/200");
callback.succeeded();
}

View File

@ -369,7 +369,7 @@ public class HttpClientTest extends AbstractTest
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
assertTrue(HttpMethod.OPTIONS.is(request.getMethod()));
assertEquals("*", request.getPathInContext());
assertEquals("*", Request.getPathInContext(request));
callback.succeeded();
}
});
@ -392,7 +392,7 @@ public class HttpClientTest extends AbstractTest
@Override
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
if ("*".equals(request.getPathInContext()))
if ("*".equals(Request.getPathInContext(request)))
{
// Be nasty and send a relative redirect.
// Code 303 will change the method to GET.
@ -566,7 +566,7 @@ public class HttpClientTest extends AbstractTest
@Override
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
if ("/notMapped".equals(target))
org.eclipse.jetty.server.Response.writeError(request, response, callback, HttpStatus.NOT_FOUND_404);
else
@ -762,7 +762,7 @@ public class HttpClientTest extends AbstractTest
@Override
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
if (target.equals("/1"))
assertTrue(latch.await(5, TimeUnit.SECONDS));
else if (target.equals("/2"))

View File

@ -424,7 +424,7 @@ public class HttpClientTimeoutTest extends AbstractTest
@Override
public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
{
if (request.getPathInContext().startsWith("/one"))
if (org.eclipse.jetty.server.Request.getPathInContext(request).startsWith("/one"))
Thread.sleep(3 * timeout);
callback.succeeded();
}
@ -460,7 +460,7 @@ public class HttpClientTimeoutTest extends AbstractTest
@Override
public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
{
if (request.getPathInContext().startsWith("/one"))
if (org.eclipse.jetty.server.Request.getPathInContext(request).startsWith("/one"))
serverLatch.await();
callback.succeeded();
}

View File

@ -585,7 +585,7 @@ public class HttpClientTransportDynamicTest
int proxyPort = connector.getLocalPort();
// The proxy speaks both http/1.1 and h2c.
Origin.Protocol proxyProtocol = new Origin.Protocol(List.of("http/1.1", "h2c"), false);
client.getProxyConfiguration().getProxies().add(new HttpProxy(new Origin.Address("localhost", proxyPort), false, proxyProtocol));
client.getProxyConfiguration().addProxy(new HttpProxy(new Origin.Address("localhost", proxyPort), false, proxyProtocol));
// Make an upgrade request from HTTP/1.1 to H2C.
int serverPort = proxyPort + 1; // Any port will do.

View File

@ -174,7 +174,7 @@ public class UnixDomainTest
ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", fakeProxyPort));
httpClient.getProxyConfiguration().addProxy(new HttpProxy("localhost", fakeProxyPort));
httpClient.start();
try
{
@ -205,7 +205,7 @@ public class UnixDomainTest
assertThat(endPoint, Matchers.instanceOf(ProxyConnectionFactory.ProxyEndPoint.class));
assertThat(endPoint.getLocalSocketAddress(), Matchers.instanceOf(unixDomainSocketAddressClass));
assertThat(endPoint.getRemoteSocketAddress(), Matchers.instanceOf(unixDomainSocketAddressClass));
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
if ("/v1".equals(target))
{
// As PROXYv1 does not support UNIX, the wrapped EndPoint data is used.

View File

@ -170,60 +170,60 @@ public interface Attributes
*/
class Wrapper implements Attributes
{
protected final Attributes _attributes;
private final Attributes _wrapped;
public Wrapper(Attributes attributes)
public Wrapper(Attributes wrapped)
{
_attributes = attributes;
_wrapped = wrapped;
}
public Attributes getWrapped()
{
return _attributes;
return _wrapped;
}
@Override
public Object removeAttribute(String name)
{
return _attributes.removeAttribute(name);
return getWrapped().removeAttribute(name);
}
@Override
public Object setAttribute(String name, Object attribute)
{
return _attributes.setAttribute(name, attribute);
return getWrapped().setAttribute(name, attribute);
}
@Override
public Object getAttribute(String name)
{
return _attributes.getAttribute(name);
return getWrapped().getAttribute(name);
}
@Override
public Set<String> getAttributeNameSet()
{
return _attributes.getAttributeNameSet();
return getWrapped().getAttributeNameSet();
}
@Override
public void clearAttributes()
{
_attributes.clearAttributes();
getWrapped().clearAttributes();
}
// TODO: remove? or fix (don't want the wrapped and wrapper to match)
@Override
public int hashCode()
{
return _attributes.hashCode();
return getWrapped().hashCode();
}
// TODO: remove? or fix (don't want the wrapped and wrapper to match)
@Override
public boolean equals(Object obj)
{
return _attributes.equals(obj);
return getWrapped().equals(obj);
}
}

View File

@ -697,6 +697,29 @@ public final class URIUtil
}
}
/**
* @param path The path to check for validity
* @return True if the path does not contain any invalid path characters
*/
public static boolean isPathValid(String path)
{
if (path == null)
return true;
int end = path.length();
for (int i = 0; i < end; i++)
{
char c = path.charAt(i);
switch (c)
{
case '?' :
case '#' :
return false;
}
}
return true;
}
/**
* Test if codepoint is safe and unambiguous to pass as input to {@link URI}
*

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.ee9.quickstart;
package org.eclipse.jetty.util.resource;
import java.io.File;
import java.io.IOException;
@ -22,18 +22,15 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -165,81 +162,79 @@ public class AttributeNormalizer
}
}
private static Comparator<Attribute> attrComparator = new Comparator<Attribute>()
private static final Comparator<Attribute> attrComparator = (o1, o2) ->
{
@Override
public int compare(Attribute o1, Attribute o2)
if ((o1.value == null) && (o2.value != null))
{
if ((o1.value == null) && (o2.value != null))
{
return -1;
}
if ((o1.value != null) && (o2.value == null))
{
return 1;
}
if ((o1.value == null) && (o2.value == null))
{
return 0;
}
// Different lengths?
int diff = o2.value.length() - o1.value.length();
if (diff != 0)
{
return diff;
}
// Different names?
diff = o2.value.compareTo(o1.value);
if (diff != 0)
{
return diff;
}
// The paths are the same, base now on weight
return o2.weight - o1.weight;
return -1;
}
if ((o1.value != null) && (o2.value == null))
{
return 1;
}
if (o1.value == null)
{
return 0;
}
// Different lengths?
int diff = o2.value.length() - o1.value.length();
if (diff != 0)
{
return diff;
}
// Different names?
diff = o2.value.compareTo(o1.value);
if (diff != 0)
{
return diff;
}
// The paths are the same, base now on weight
return o2.weight - o1.weight;
};
private URI warURI;
private Map<String, Attribute> attributes = new HashMap<>();
private List<PathAttribute> paths = new ArrayList<>();
private List<URIAttribute> uris = new ArrayList<>();
private final List<PathAttribute> paths = new ArrayList<>();
private final List<URIAttribute> uris = new ArrayList<>();
public AttributeNormalizer(Resource baseResource)
{
if (baseResource == null)
throw new IllegalArgumentException("No base resource!");
warURI = toCanonicalURI(baseResource.getURI());
if (!warURI.isAbsolute())
throw new IllegalArgumentException("WAR URI is not absolute: " + warURI);
addSystemProperty("jetty.base", 9);
addSystemProperty("jetty.home", 8);
addSystemProperty("user.home", 7);
addSystemProperty("user.dir", 6);
if (warURI.getScheme().equalsIgnoreCase("file"))
paths.add(new PathAttribute("WAR.path", toCanonicalPath(new File(warURI).toString()), 10));
uris.add(new URIAttribute("WAR.uri", warURI, 9)); // preferred encoding
uris.add(new URIAttribute("WAR", warURI, 8)); // legacy encoding
Set<Path> rootPaths = new HashSet<>();
for (Resource r : baseResource)
{
if (r instanceof MountedPathResource mpr && rootPaths.contains(mpr.getContainerPath()))
return;
Collections.sort(paths, attrComparator);
Collections.sort(uris, attrComparator);
URI warURI = toCanonicalURI(r.getURI());
if (!warURI.isAbsolute())
throw new IllegalArgumentException("WAR URI is not absolute: " + warURI);
Stream.concat(paths.stream(), uris.stream()).forEach(a -> attributes.put(a.key, a));
Path path = r.getPath();
if (path != null)
{
rootPaths.add(path);
paths.add(new PathAttribute("WAR.path", toCanonicalPath(path), 10));
}
uris.add(new URIAttribute("WAR.uri", warURI, 9)); // preferred encoding
uris.add(new URIAttribute("WAR", warURI, 8)); // legacy encoding
}
paths.sort(attrComparator);
uris.sort(attrComparator);
if (LOG.isDebugEnabled())
{
for (Attribute attr : attributes.values())
{
LOG.debug(attr.toString());
}
}
Stream.concat(paths.stream(), uris.stream()).map(Object::toString).forEach(LOG::debug);
}
private void addSystemProperty(String key, int weight)
@ -352,7 +347,7 @@ public class AttributeNormalizer
return String.format("${%s}", a.key);
String s = uPath.substring(aPath.length());
if (s.length() > 0 && s.charAt(0) != '/')
if (s.charAt(0) != '/')
continue;
return String.format("${%s}%s", a.key, s);
@ -375,95 +370,79 @@ public class AttributeNormalizer
}
if (path.startsWith(a.path))
return String.format("${%s}%c%s", a.key, File.separatorChar, a.path.relativize(path).toString());
return String.format("${%s}%c%s", a.key, File.separatorChar, a.path.relativize(path));
}
return path.toString();
}
public String expand(String str)
{
return expand(str, new Stack<String>());
}
public String expand(String str, Stack<String> seenStack)
{
if (str == null)
{
return str;
}
if (str.indexOf("${") < 0)
if (!str.contains("${"))
{
// Contains no potential expressions.
return str;
}
Matcher mat = __propertyPattern.matcher(str);
StringBuilder expanded = new StringBuilder();
int offset = 0;
String property;
String value;
while (mat.find(offset))
if (mat.find(0))
{
property = mat.group(1);
// Loop detection
if (seenStack.contains(property))
{
StringBuilder err = new StringBuilder();
err.append("Property expansion loop detected: ");
int idx = seenStack.lastIndexOf(property);
for (int i = idx; i < seenStack.size(); i++)
{
err.append(seenStack.get(i));
err.append(" -> ");
}
err.append(property);
throw new RuntimeException(err.toString());
}
seenStack.push(property);
// find property name
expanded.append(str.subSequence(offset, mat.start()));
// get property value
value = getString(property);
if (value == null)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to expand: {}", property);
expanded.append(mat.group());
}
else
{
// recursively expand
value = expand(value, seenStack);
expanded.append(value);
}
// update offset
offset = mat.end();
String prefix = str.substring(0, mat.start());
String property = mat.group(1);
String suffix = str.substring(mat.end());
str = expand(prefix, property, suffix);
}
// leftover
expanded.append(str.substring(offset));
return StringUtil.replace(expanded.toString(), "$$", "$");
return StringUtil.replace(str, "$$", "$");
}
private String getString(String property)
private String expand(String prefix, String property, String suffix)
{
if (property == null)
{
return null;
for (URIAttribute attr : uris)
{
if (property.equals(attr.key))
{
try
{
String uri = prefix + attr.value + suffix;
Resource resource = ResourceFactory.root().newResource(uri);
if (resource.exists())
return uri;
}
catch (Exception ex)
{
if (LOG.isDebugEnabled())
LOG.trace("ignored", ex);
}
}
}
Attribute a = attributes.get(property);
if (a != null)
return a.value;
for (PathAttribute attr : paths)
{
if (property.equals(attr.key))
{
String path = prefix + attr.value + suffix;
if (Files.exists(Path.of(path)))
return path;
}
}
// Use system properties next
return System.getProperty(property);
String system = System.getProperty(property);
if (system != null)
return prefix + system + suffix;
String unexpanded = prefix + "${" + property + "}" + suffix;
LOG.warn("Cannot expand: {}", unexpanded);
return unexpanded;
}
}

View File

@ -13,11 +13,8 @@
package org.eclipse.jetty.util.resource;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
@ -30,32 +27,34 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.util.URIUtil;
/**
* A collection of Resources.
* Allows webapps to have multiple sources.
* The first resource in the collection is the main resource.
* If a resource is not found in the main resource, it looks it up in
* the order the provided in the constructors
* Multiple resource directories presented as a single Resource.
*/
public class ResourceCollection extends Resource
public class CombinedResource extends Resource
{
private final List<Resource> _resources;
/**
* Instantiates a new resource collection.
*
* @param resources the resources to be added to collection
* <p>Make a Resource containing a combination of other resources</p>
* @param resources multiple resources to combine as a single resource. Typically, they are directories.
* @return A Resource of multiple resources or a single resource if only 1 is passed, or null if none are passed
* @see CombinedResource
*/
ResourceCollection(List<Resource> resources)
static Resource combine(List<Resource> resources)
{
List<Resource> res = new ArrayList<>();
gatherUniqueFlatResourceList(res, resources);
_resources = Collections.unmodifiableList(res);
resources = CombinedResource.gatherUniqueFlatResourceList(resources);
if (resources == null || resources.isEmpty())
return null;
if (resources.size() == 1)
return resources.get(0);
return new CombinedResource(resources);
}
private static void gatherUniqueFlatResourceList(List<Resource> unique, List<Resource> resources)
static List<Resource> gatherUniqueFlatResourceList(List<Resource> resources)
{
if (resources == null || resources.isEmpty())
throw new IllegalArgumentException("Empty Resource collection");
return null;
List<Resource> unique = new ArrayList<>(resources.size());
for (Resource r : resources)
{
@ -64,9 +63,9 @@ public class ResourceCollection extends Resource
throw new IllegalArgumentException("Null Resource entry encountered");
}
if (r instanceof ResourceCollection resourceCollection)
if (r instanceof CombinedResource resourceCollection)
{
gatherUniqueFlatResourceList(unique, resourceCollection.getResources());
unique.addAll(gatherUniqueFlatResourceList(resourceCollection.getResources()));
}
else
{
@ -89,6 +88,19 @@ public class ResourceCollection extends Resource
unique.add(r);
}
}
return unique;
}
private final List<Resource> _resources;
/**
* Instantiates a new resource collection.
*
* @param resources the resources to be added to collection
*/
CombinedResource(List<Resource> resources)
{
_resources = Collections.unmodifiableList(resources);
}
/**
@ -127,127 +139,67 @@ public class ResourceCollection extends Resource
ArrayList<Resource> resources = null;
// Attempt a simple (single) Resource lookup that exists
Resource addedResource = null;
Resource resolved = null;
for (Resource res : _resources)
{
addedResource = res.resolve(subUriPath);
if (Resources.missing(addedResource))
resolved = res.resolve(subUriPath);
if (Resources.missing(resolved))
continue; // skip, doesn't exist
if (!addedResource.isDirectory())
return addedResource; // Return simple (non-directory) Resource
if (!resolved.isDirectory())
return resolved; // Return simple (non-directory) Resource
if (resources == null)
resources = new ArrayList<>();
resources.add(addedResource);
resources.add(resolved);
}
if (resources == null)
return addedResource; // This will not exist
return resolved; // This will not exist
if (resources.size() == 1)
return resources.get(0);
return new ResourceCollection(resources);
return new CombinedResource(resources);
}
@Override
public boolean exists()
{
for (Resource r : _resources)
{
if (r.exists())
{
return true;
}
}
return false;
return _resources.stream().anyMatch(Resource::exists);
}
@Override
public Path getPath()
{
for (Resource r : _resources)
{
Path p = r.getPath();
if (p != null)
return p;
}
return null;
}
@Override
public InputStream newInputStream() throws IOException
{
for (Resource r : _resources)
{
if (!r.exists())
{
// Skip, cannot open anyway
continue;
}
InputStream is = r.newInputStream();
if (is != null)
{
return is;
}
}
throw new FileNotFoundException("Resource does not exist");
}
@Override
public ReadableByteChannel newReadableByteChannel() throws IOException
{
for (Resource r : _resources)
{
ReadableByteChannel channel = r.newReadableByteChannel();
if (channel != null)
{
return channel;
}
}
return null;
}
@Override
public String getName()
{
for (Resource r : _resources)
{
String name = r.getName();
if (name != null)
{
return name;
}
}
return null;
}
@Override
public String getFileName()
{
String filename = null;
// return a non-null filename only if all resources agree on the same name.
for (Resource r : _resources)
{
String filename = r.getFileName();
if (filename != null)
{
return filename;
}
String fn = r.getFileName();
if (fn == null)
return null;
if (filename == null)
filename = fn;
else if (!filename.equals(fn))
return null;
}
return null;
return filename;
}
@Override
public URI getURI()
{
for (Resource r : _resources)
{
URI uri = r.getURI();
if (uri != null)
{
return uri;
}
}
return null;
}
@ -326,7 +278,7 @@ public class ResourceCollection extends Resource
return true;
if (o == null || getClass() != o.getClass())
return false;
ResourceCollection other = (ResourceCollection)o;
CombinedResource other = (CombinedResource)o;
return Objects.equals(_resources, other._resources);
}
@ -343,7 +295,7 @@ public class ResourceCollection extends Resource
public String toString()
{
return _resources.stream()
.map(Resource::getName)
.map(Resource::toString)
.collect(Collectors.joining(", ", "[", "]"));
}

View File

@ -113,11 +113,8 @@ public abstract class Resource implements Iterable<Resource>
public abstract boolean isContainedIn(Resource r);
/**
* Return an Iterator of all Resource's referenced in this Resource.
*
* <p>
* This is meaningful if you have a Composite Resource, otherwise it will be a single entry Iterator.
* </p>
* <p>Return an Iterator of all Resource's referenced in this Resource.</p>
* <p>This is meaningful if you have a Composite Resource, otherwise it will be a single entry Iterator of this resource.</p>
*
* @return the iterator of Resources.
*/
@ -198,22 +195,28 @@ public abstract class Resource implements Iterable<Resource>
/**
* Creates a new input stream to the resource.
*
* @return an input stream to the resource
* @throws IOException if unable to open the input stream
* @return an input stream to the resource or null if one is not available.
* @throws IOException if there is a problem opening the input stream
*/
public InputStream newInputStream() throws IOException
{
return Files.newInputStream(getPath(), StandardOpenOption.READ);
Path path = getPath();
if (path == null)
return null;
return Files.newInputStream(path, StandardOpenOption.READ);
}
/**
* Readable ByteChannel for the resource.
*
* @return an readable bytechannel to the resource or null if one is not available.
* @return a readable {@link java.nio.channels.ByteChannel} to the resource or null if one is not available.
* @throws IOException if unable to open the readable bytechannel for the resource.
*/
public ReadableByteChannel newReadableByteChannel() throws IOException
{
Path path = getPath();
if (path == null)
return null;
return Files.newByteChannel(getPath(), StandardOpenOption.READ);
}

View File

@ -35,27 +35,23 @@ public interface ResourceFactory
/**
* <p>Make a Resource containing a collection of other resources</p>
* @param resources multiple resources to combine as a single resource. Typically, they are directories.
* @return A Resource of multiple resources.
* @see ResourceCollection
* @return A Resource of multiple resources or a single resource if only 1 is passed, or null if none are passed
* @see CombinedResource
*/
static ResourceCollection combine(List<Resource> resources)
static Resource combine(List<Resource> resources)
{
if (resources == null || resources.isEmpty())
throw new IllegalArgumentException("No resources");
return new ResourceCollection(resources);
return CombinedResource.combine(resources);
}
/**
* <p>Make a Resource containing a collection of other resources</p>
* @param resources multiple resources to combine as a single resource. Typically, they are directories.
* @return A Resource of multiple resources.
* @see ResourceCollection
* @see CombinedResource
*/
static ResourceCollection combine(Resource... resources)
static Resource combine(Resource... resources)
{
if (resources == null || resources.length == 0)
throw new IllegalArgumentException("No resources");
return new ResourceCollection(List.of(resources));
return CombinedResource.combine(List.of(resources));
}
/**
@ -203,12 +199,12 @@ public interface ResourceFactory
}
/**
* Construct a ResourceCollection from a list of URIs
* Construct a possible {@link CombinedResource} from a list of URIs
*
* @param uris the URIs
* @return the Resource for the provided path
*/
default ResourceCollection newResource(List<URI> uris)
default Resource newResource(List<URI> uris)
{
if ((uris == null) || (uris.isEmpty()))
throw new IllegalArgumentException("List of URIs is invalid");

View File

@ -20,13 +20,13 @@ import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.LongAdder;
import org.eclipse.jetty.util.AtomicBiInteger;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.VirtualThreads;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.TryExecutor;
@ -98,12 +98,9 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
/**
* The production state of the strategy.
*/
private enum State
{
IDLE, // No tasks or producers.
PRODUCING, // There is an active producing thread.
REPRODUCING // There is an active producing thread and demand for more production.
}
static final int IDLE = 0; // No tasks or producers.
static final int PRODUCING = 1; // There is an active producing thread.
static final int REPRODUCING = 2; // There is an active producing thread and demand for more production.
/**
* The sub-strategies used by the strategy to consume tasks that are produced.
@ -128,7 +125,6 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
EXECUTE_PRODUCE_CONSUME
}
private final AutoLock _lock = new AutoLock();
private final LongAdder _pcMode = new LongAdder();
private final LongAdder _picMode = new LongAdder();
private final LongAdder _pecMode = new LongAdder();
@ -138,8 +134,7 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
private final TryExecutor _tryExecutor;
private final Runnable _runPendingProducer = () -> tryProduce(true);
private boolean _useVirtualThreads;
private State _state = State.IDLE;
private boolean _pending;
private final AtomicBiInteger _state = new AtomicBiInteger();
/**
* @param producer The producer of tasks to be consumed.
@ -167,26 +162,33 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
public void dispatch()
{
boolean execute = false;
try (AutoLock l = _lock.lock())
loop: while (true)
{
switch (_state)
long biState = _state.get();
int state = AtomicBiInteger.getLo(biState);
int pending = AtomicBiInteger.getHi(biState);
switch (state)
{
case IDLE:
if (!_pending)
if (pending <= 0)
{
_pending = true;
if (!_state.compareAndSet(biState, pending + 1, state))
continue;
execute = true;
}
break;
break loop;
case PRODUCING:
_state = State.REPRODUCING;
break;
if (!_state.compareAndSet(biState, pending, REPRODUCING))
continue;
break loop;
default:
break;
break loop;
}
}
if (LOG.isDebugEnabled())
LOG.debug("{} dispatch {}", this, execute);
if (execute)
@ -209,39 +211,47 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
if (LOG.isDebugEnabled())
LOG.debug("{} tryProduce {}", this, wasPending);
// Takes the lock to atomically check if the thread can produce.
try (AutoLock l = _lock.lock())
// check if the thread can produce.
loop: while (true)
{
long biState = _state.get();
int state = AtomicBiInteger.getLo(biState);
int pending = AtomicBiInteger.getHi(biState);
// If the calling thread was the pending producer, there is no longer one pending.
if (wasPending)
_pending = false;
pending--;
switch (_state)
switch (state)
{
case IDLE:
// The strategy was IDLE, so this thread can become the producer.
_state = State.PRODUCING;
break;
if (!_state.compareAndSet(biState, pending, PRODUCING))
continue;
break loop;
case PRODUCING:
// The strategy is already producing, so another thread must be the producer.
// However, it may be just about to stop being the producer so we set the
// REPRODUCING state to force it to produce at least once more.
_state = State.REPRODUCING;
if (!_state.compareAndSet(biState, pending, REPRODUCING))
continue;
return;
case REPRODUCING:
// Another thread is already producing and will already try again to produce.
if (!_state.compareAndSet(biState, pending, state))
continue;
return;
default:
throw new IllegalStateException(toStringLocked());
throw new IllegalStateException(toString(biState));
}
}
// Determine the thread's invocation type once, outside of the production loop.
boolean nonBlocking = Invocable.isNonBlockingInvocation();
while (isRunning())
running: while (isRunning())
{
try
{
@ -250,24 +260,30 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
// If we did not produce a task
if (task == null)
{
// take the lock to atomically determine if we should keep producing.
try (AutoLock l = _lock.lock())
// determine if we should keep producing.
while (true)
{
switch (_state)
long biState = _state.get();
int state = AtomicBiInteger.getLo(biState);
int pending = AtomicBiInteger.getHi(biState);
switch (state)
{
case PRODUCING:
// The calling thread was the only producer, so it is now IDLE and we stop producing.
_state = State.IDLE;
if (!_state.compareAndSet(biState, pending, IDLE))
continue;
return;
case REPRODUCING:
// Another thread may have queued a task and tried to produce
// so the calling thread should continue to produce.
_state = State.PRODUCING;
continue;
if (!_state.compareAndSet(biState, pending, PRODUCING))
continue;
continue running;
default:
throw new IllegalStateException(toStringLocked());
throw new IllegalStateException(toString(biState));
}
}
}
@ -303,53 +319,90 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
return SubStrategy.PRODUCE_CONSUME;
case EITHER:
{
// The produced task may be run either as blocking or non blocking.
// If the calling producing thread is already non-blocking, use PC.
if (nonBlocking)
return SubStrategy.PRODUCE_CONSUME;
// Take the lock to atomically check if a pending producer is available.
try (AutoLock l = _lock.lock())
// check if a pending producer is available.
int executed = 0;
while (true)
{
long biState = _state.get();
int state = AtomicBiInteger.getLo(biState);
int pending = AtomicBiInteger.getHi(biState);
// If a pending producer is available or one can be started
if (_pending || _tryExecutor.tryExecute(_runPendingProducer))
pending += executed;
if (pending <= 0 && _tryExecutor.tryExecute(_runPendingProducer))
{
executed++;
pending++;
}
if (pending > 0)
{
// Use EPC: the producer directly consumes the task, which may block
// and then races with the pending producer to resume production.
_pending = true;
_state = State.IDLE;
if (!_state.compareAndSet(biState, pending, IDLE))
continue;
return SubStrategy.EXECUTE_PRODUCE_CONSUME;
}
if (!_state.compareAndSet(biState, pending, state))
continue;
break;
}
// Otherwise use PIC: the producer consumes the task
// in non-blocking mode and then resumes production.
return SubStrategy.PRODUCE_INVOKE_CONSUME;
}
case BLOCKING:
{
// The produced task may block.
// If the calling producing thread may also block
if (!nonBlocking)
{
// Take the lock to atomically check if a pending producer is available.
try (AutoLock l = _lock.lock())
// check if a pending producer is available.
int executed = 0;
while (true)
{
long biState = _state.get();
int state = AtomicBiInteger.getLo(biState);
int pending = AtomicBiInteger.getHi(biState);
// If a pending producer is available or one can be started
if (_pending || _tryExecutor.tryExecute(_runPendingProducer))
pending += executed;
if (pending <= 0 && _tryExecutor.tryExecute(_runPendingProducer))
{
executed++;
pending++;
}
// If a pending producer is available or one can be started
if (pending > 0)
{
// use EPC: The producer directly consumes the task, which may block
// and then races with the pending producer to resume production.
_pending = true;
_state = State.IDLE;
if (!_state.compareAndSet(biState, pending, IDLE))
continue;
return SubStrategy.EXECUTE_PRODUCE_CONSUME;
}
if (!_state.compareAndSet(biState, pending, state))
continue;
break;
}
}
// Otherwise use PEC: the task is consumed by the executor and the producer continues to produce.
return SubStrategy.PRODUCE_EXECUTE_CONSUME;
}
default:
throw new IllegalStateException(String.format("taskType=%s %s", taskType, this));
@ -390,19 +443,25 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
runTask(task);
// Race the pending producer to produce again.
try (AutoLock l = _lock.lock())
while (true)
{
if (_state == State.IDLE)
long biState = _state.get();
int state = AtomicBiInteger.getLo(biState);
int pending = AtomicBiInteger.getHi(biState);
if (state == IDLE)
{
// We beat the pending producer, so we will become the producer instead.
// The pending produce will become a noop if it arrives whilst we are producing,
// or it may take over if we subsequently do another EPC consumption.
_state = State.PRODUCING;
if (!_state.compareAndSet(biState, pending, PRODUCING))
continue;
return true;
}
// The pending producer is now producing, so this thread no longer produces.
return false;
}
// The pending producer is now producing, so this thread no longer produces.
return false;
default:
throw new IllegalStateException(String.format("ss=%s %s", subStrategy, this));
@ -521,10 +580,7 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
@ManagedAttribute(value = "whether this execution strategy is idle", readonly = true)
public boolean isIdle()
{
try (AutoLock l = _lock.lock())
{
return _state == State.IDLE;
}
return _state.getLo() == IDLE;
}
@ManagedOperation(value = "resets the task counts", impact = "ACTION")
@ -539,17 +595,14 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
@Override
public String toString()
{
try (AutoLock l = _lock.lock())
{
return toStringLocked();
}
return toString(_state.get());
}
public String toStringLocked()
public String toString(long biState)
{
StringBuilder builder = new StringBuilder();
getString(builder);
getState(builder);
getState(builder, biState);
return builder.toString();
}
@ -563,11 +616,20 @@ public class AdaptiveExecutionStrategy extends ContainerLifeCycle implements Exe
builder.append('/');
}
private void getState(StringBuilder builder)
private void getState(StringBuilder builder, long biState)
{
builder.append(_state);
int state = AtomicBiInteger.getLo(biState);
int pending = AtomicBiInteger.getHi(biState);
builder.append(
switch (state)
{
case IDLE -> "IDLE";
case PRODUCING -> "PRODUCING";
case REPRODUCING -> "REPRODUCING";
default -> "UNKNOWN(%d)".formatted(state);
});
builder.append("/p=");
builder.append(_pending);
builder.append(pending);
builder.append('/');
builder.append(_tryExecutor);
builder.append("[pc=");

View File

@ -11,8 +11,9 @@
// ========================================================================
//
package org.eclipse.jetty.ee10.quickstart;
package org.eclipse.jetty.util.resource;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
@ -26,11 +27,9 @@ import java.util.stream.Stream;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@ -43,7 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class AttributeNormalizerTest
{
public static Stream<Arguments> scenarios()
public static Stream<Arguments> scenarios() throws IOException
{
final List<Arguments> data = new ArrayList<>();
final String arch = String.format("%s/%s", System.getProperty("os.name"), System.getProperty("os.arch"));
@ -55,18 +54,22 @@ public class AttributeNormalizerTest
// ------
title = "Typical Setup";
jettyHome = asTargetPath(title, "jetty-distro");
jettyBase = asTargetPath(title, "jetty-distro/demo.base");
war = asTargetPath(title, "jetty-distro/demo.base/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
jettyHome = asTargetPath(title, "jetty-typical");
jettyBase = asTargetPath(title, "jetty-typical/demo.base");
war = asTargetPath(title, "jetty-typical/demo.base/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, resourceFactory.newResource(war))));
// ------
title = "Old Setup";
jettyHome = asTargetPath(title, "jetty-distro");
jettyBase = asTargetPath(title, "jetty-distro");
war = asTargetPath(title, "jetty-distro/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
jettyHome = asTargetPath(title, "jetty-old");
jettyBase = asTargetPath(title, "jetty-old");
war = asTargetPath(title, "jetty-old/webapps/FOO");
if (!Files.exists(war.resolve("index.html")))
{
Files.createFile(war.resolve("index.html"));
Files.createFile(war.resolve("favicon.ico"));
}
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, resourceFactory.newResource(war))));
// ------
// This puts the jetty.home inside the jetty.base
@ -74,8 +77,7 @@ public class AttributeNormalizerTest
jettyHome = asTargetPath(title, "app/dist");
jettyBase = asTargetPath(title, "app");
war = asTargetPath(title, "app/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, resourceFactory.newResource(war))));
// ------
// This tests a path scenario often seen on various automatic deployments tooling
@ -84,8 +86,16 @@ public class AttributeNormalizerTest
jettyHome = asTargetPath(title, "app%2Fnasty/dist");
jettyBase = asTargetPath(title, "app%2Fnasty/base");
war = asTargetPath(title, "app%2Fnasty/base/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, resourceFactory.newResource(war))));
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
// ------
title = "ResourceCollection Setup";
jettyHome = asTargetPath(title, "jetty-collection");
jettyBase = asTargetPath(title, "jetty-collection/demo.base");
Path warA = asTargetPath(title, "jetty-collection/demo.base/webapps/WarA");
Path warB = asTargetPath(title, "jetty-collection/demo.base/webapps/WarB");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase,
ResourceFactory.combine(resourceFactory.newResource(warA), resourceFactory.newResource(warB)))));
return data.stream();
}
@ -166,7 +176,9 @@ public class AttributeNormalizerTest
public void testNormalizeWarAsString(final Scenario scenario)
{
// Normalize WAR as String path
assertNormalize(scenario, scenario.war.toString(), scenario.war.toString());
Path path = scenario.war.getPath();
Assumptions.assumeTrue(path != null);
assertNormalize(scenario, path.toString(), path.toString());
}
@ParameterizedTest
@ -259,12 +271,22 @@ public class AttributeNormalizerTest
assertExpandPath(scenario, "${jetty.home}", scenario.jettyHome.toString());
}
@ParameterizedTest
@MethodSource("scenarios")
public void testExpandWebInfAsURI(final Scenario scenario)
{
// Expand
assertExpandURI(scenario, "${WAR.uri}/WEB-INF/web.xml", scenario.webXml.toUri());
assertExpandURI(scenario, "${WAR.uri}/WEB-INF/test.tld", scenario.testTld.toUri());
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeWarAsURI(final Scenario scenario)
{
// Normalize WAR as URI
URI testWarURI = scenario.war.toUri();
URI testWarURI = scenario.war.getURI();
Assumptions.assumeTrue(testWarURI != null);
assertNormalize(scenario, testWarURI, "${WAR.uri}");
}
@ -273,7 +295,7 @@ public class AttributeNormalizerTest
public void testNormalizeWarDeepAsPath(final Scenario scenario)
{
// Normalize WAR deep path as File
Path testWarDeep = scenario.war.resolve("deep/ref");
Path testWarDeep = scenario.war.resolve("deep/ref").getPath();
assertNormalize(scenario, testWarDeep, "${WAR.path}" + FS.separators("/deep/ref"));
}
@ -282,7 +304,7 @@ public class AttributeNormalizerTest
public void testNormalizeWarDeepAsString(final Scenario scenario)
{
// Normalize WAR deep path as String
Path testWarDeep = scenario.war.resolve("deep/ref");
Path testWarDeep = scenario.war.resolve("deep/ref").getPath();
assertNormalize(scenario, testWarDeep.toString(), testWarDeep.toString());
}
@ -291,30 +313,22 @@ public class AttributeNormalizerTest
public void testNormalizeWarDeepAsURI(final Scenario scenario)
{
// Normalize WAR deep path as URI
Path testWarDeep = scenario.war.resolve("deep/ref");
Path testWarDeep = scenario.war.resolve("deep/ref").getPath();
assertNormalize(scenario, testWarDeep.toUri(), "${WAR.uri}/deep/ref");
}
@ParameterizedTest
@MethodSource("scenarios")
public void testExpandWarDeep(final Scenario scenario)
{
// Expand WAR deep path
Path testWarDeep = scenario.war.resolve("deep/ref");
URI uri = URI.create("jar:" + testWarDeep.toUri().toASCIIString() + "!/other/file");
assertExpandURI(scenario, "jar:${WAR.uri}/deep/ref!/other/file", uri);
}
public static class Scenario
{
private final Path jettyHome;
private final Path jettyBase;
private final Path war;
private final Resource war;
private Path webXml;
private Path testTld;
private final String arch;
private final String title;
private final AttributeNormalizer normalizer;
public Scenario(String arch, String title, Path jettyHome, Path jettyBase, Path war)
public Scenario(String arch, String title, Path jettyHome, Path jettyBase, Resource war)
{
this.arch = arch;
this.title = title;
@ -326,15 +340,48 @@ public class AttributeNormalizerTest
assertTrue(Files.exists(this.jettyHome));
assertTrue(Files.exists(this.jettyBase));
assertTrue(Files.exists(this.war));
assertTrue(war.exists());
// Set some System Properties that AttributeNormalizer expects
System.setProperty("jetty.home", jettyHome.toString());
System.setProperty("jetty.base", jettyBase.toString());
for (Resource w : war)
{
try
{
Path webinf = w.getPath().resolve("WEB-INF");
if (!Files.exists(webinf))
Files.createDirectory(webinf);
Path deep = w.getPath().resolve("deep");
if (!Files.exists(deep))
Files.createDirectory(deep);
Path ref = deep.resolve("ref");
if (!Files.exists(ref))
Files.createFile(ref);
if (w.getFileName().equals("FOO") || w.getFileName().equals("WarA"))
{
webXml = webinf.resolve("web.xml");
if (!Files.exists(webXml))
Files.createFile(webXml);
}
if (w.getFileName().equals("FOO") || w.getFileName().equals("WarB"))
{
testTld = webinf.resolve("test.tld");
if (!Files.exists(testTld))
Files.createFile(testTld);
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
// Setup normalizer
Resource webresource = resourceFactory.newResource(war);
this.normalizer = new AttributeNormalizer(webresource);
this.normalizer = new AttributeNormalizer(war);
}
@Override

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.ee9.quickstart;
package org.eclipse.jetty.util.resource;
import java.net.URI;
import java.util.ArrayList;

View File

@ -52,7 +52,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
public class ResourceCollectionTest
public class CombinedResourceTest
{
private final ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable();
public WorkDir workDir;
@ -78,7 +78,7 @@ public class ResourceCollectionTest
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
ResourceCollection rc = ResourceFactory.combine(
Resource rc = ResourceFactory.combine(
resourceFactory.newResource(one),
resourceFactory.newResource(two),
resourceFactory.newResource(three)
@ -127,7 +127,7 @@ public class ResourceCollectionTest
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
ResourceCollection rc = ResourceFactory.combine(
Resource rc = ResourceFactory.combine(
resourceFactory.newResource(one),
resourceFactory.newResource(two),
resourceFactory.newResource(three)
@ -135,8 +135,8 @@ public class ResourceCollectionTest
// This should return a ResourceCollection with 3 `/dir/` sub-directories.
Resource r = rc.resolve("dir");
assertTrue(r instanceof ResourceCollection);
rc = (ResourceCollection)r;
assertTrue(r instanceof CombinedResource);
rc = (CombinedResource)r;
assertEquals(getContent(rc, "1.txt"), "1 - one (in dir)");
assertEquals(getContent(rc, "2.txt"), "2 - two (in dir)");
assertEquals(getContent(rc, "3.txt"), "3 - three (in dir)");
@ -149,7 +149,7 @@ public class ResourceCollectionTest
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
ResourceCollection rc = ResourceFactory.combine(
Resource rc = ResourceFactory.combine(
resourceFactory.newResource(one),
resourceFactory.newResource(two),
resourceFactory.newResource(three)
@ -175,7 +175,7 @@ public class ResourceCollectionTest
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
Path twoDir = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two/dir");
ResourceCollection rc1 = ResourceFactory.combine(
Resource rc1 = ResourceFactory.combine(
List.of(
resourceFactory.newResource(one),
resourceFactory.newResource(two),
@ -183,7 +183,7 @@ public class ResourceCollectionTest
)
);
ResourceCollection rc2 = ResourceFactory.combine(
Resource rc2 = ResourceFactory.combine(
List.of(
// the original ResourceCollection
rc1,
@ -202,9 +202,11 @@ public class ResourceCollectionTest
};
List<URI> actual = new ArrayList<>();
for (Resource res: rc2.getResources())
assertThat(rc2, instanceOf(CombinedResource.class));
if (rc2 instanceof CombinedResource combinedResource)
{
actual.add(res.getURI());
for (Resource res : combinedResource.getResources())
actual.add(res.getURI());
}
assertThat(actual, contains(expected));
}
@ -307,7 +309,7 @@ public class ResourceCollectionTest
// Since this is user space, we cannot know ahead of time what
// this list contains, so we mount because we assume there
// will be necessary things to mount
ResourceCollection rc = resourceFactory.newResource(uris);
Resource rc = resourceFactory.newResource(uris);
assertThat(getContent(rc, "test.txt"), is("Test"));
}
@ -333,7 +335,7 @@ public class ResourceCollectionTest
// Since this is user space, we cannot know ahead of time what
// this list contains, so we mount because we assume there
// will be necessary things to mount
ResourceCollection rc = resourceFactory.newResource(uris);
Resource rc = resourceFactory.newResource(uris);
assertThat(getContent(rc, "test.txt"), is("Test inside lib-foo.jar"));
assertThat(getContent(rc, "testZed.txt"), is("TestZed inside lib-zed.jar"));
}

View File

@ -246,7 +246,7 @@ public class WebSocketMappings implements Dumpable, LifeCycle.Listener
*/
public boolean upgrade(Request request, Response response, Callback callback, Configuration.Customizer defaultCustomizer) throws IOException
{
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
WebSocketNegotiator negotiator = getMatchedNegotiator(target, pathSpec ->
{
// Store PathSpec resource mapping as request attribute,

View File

@ -68,7 +68,7 @@ public class WebSocketUpgradeHandler extends Handler.Wrapper
if (processor == null)
return null;
String target = request.getPathInContext();
String target = Request.getPathInContext(request);
WebSocketNegotiator negotiator = mappings.getMatchedNegotiator(target, pathSpec ->
{
// Store PathSpec resource mapping as request attribute,

View File

@ -83,7 +83,7 @@ public class WebSocketProxyTest
{
if (request.getHeaders().get("Upgrade") != null)
{
if (blockServerUpgradeRequests && request.getPathInContext().startsWith("/server"))
if (blockServerUpgradeRequests && Request.getPathInContext(request).startsWith("/server"))
{
return (req, resp, cb) -> Response.writeError(req, resp, cb, HttpStatus.INTERNAL_SERVER_ERROR_500);
}

View File

@ -13,176 +13,240 @@
package org.eclipse.jetty.ee10.demos;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.providers.ContextProvider;
import org.eclipse.jetty.ee.Deployable;
import org.eclipse.jetty.ee10.annotations.AnnotationConfiguration;
import org.eclipse.jetty.ee10.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.ee10.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.ee10.servlet.DebugListener;
import org.eclipse.jetty.ee10.servlet.security.HashLoginService;
import org.eclipse.jetty.ee10.webapp.Configurations;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.rewrite.handler.InvalidURIRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.AsyncRequestLogWriter;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LowResourceMonitor;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.component.Environment;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.xml.EnvironmentBuilder;
import org.slf4j.LoggerFactory;
/**
* Starts the Jetty Distribution's demo-base directory using entirely
* embedded jetty techniques.
*/
public class LikeJettyXml
{
// TODO
// public static Server createServer(int port, int securePort, boolean addDebugListener) throws Exception
// {
// Path configDir = Paths.get("src/main/resources/demo").toAbsolutePath();
// Path runtimeDir = Paths.get("target/embedded/" + LikeJettyXml.class.getSimpleName()).toAbsolutePath();
// mkdir(runtimeDir);
//
// // === jetty.xml ===
// // Setup Threadpool
// QueuedThreadPool threadPool = new QueuedThreadPool();
// threadPool.setMaxThreads(500);
//
// // Server
// Server server = new Server(threadPool);
//
// // Scheduler
// server.addBean(new ScheduledExecutorScheduler(null, false));
//
// // HTTP Configuration
// HttpConfiguration httpConfig = new HttpConfiguration();
// httpConfig.setSecureScheme("https");
// httpConfig.setSecurePort(securePort);
// httpConfig.setOutputBufferSize(32768);
// httpConfig.setRequestHeaderSize(8192);
// httpConfig.setResponseHeaderSize(8192);
// httpConfig.setSendServerVersion(true);
// httpConfig.setSendDateHeader(false);
// // httpConfig.addCustomizer(new ForwardedRequestCustomizer());
//
// // Handler Structure
// ContextHandlerCollection contexts = new ContextHandlerCollection();
// server.setHandler(new HandlerList(contexts, new DefaultHandler()));
//
// // === jetty-jmx.xml ===
// MBeanContainer mbContainer = new MBeanContainer(
// ManagementFactory.getPlatformMBeanServer());
// server.addBean(mbContainer);
//
// // === jetty-http.xml ===
// ServerConnector http = new ServerConnector(server,
// new HttpConnectionFactory(httpConfig));
// http.setPort(port);
// http.setIdleTimeout(30000);
// server.addConnector(http);
//
// // === jetty-https.xml ===
// // SSL Context Factory
// Path keystorePath = Paths.get("src/main/resources/etc/keystore.p12").toAbsolutePath();
// if (!Files.exists(keystorePath))
// throw new FileNotFoundException(keystorePath.toString());
// SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
// sslContextFactory.setKeyStorePath(keystorePath.toString());
// sslContextFactory.setKeyStorePassword("storepwd");
// sslContextFactory.setTrustStorePath(keystorePath.toString());
// sslContextFactory.setTrustStorePassword("storepwd");
//
// // SSL HTTP Configuration
// HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
// httpsConfig.addCustomizer(new SecureRequestCustomizer());
//
// // SSL Connector
// ServerConnector sslConnector = new ServerConnector(server,
// new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
// new HttpConnectionFactory(httpsConfig));
// sslConnector.setPort(securePort);
// server.addConnector(sslConnector);
//
// // === jetty-deploy.xml ===
// DeploymentManager deployer = new DeploymentManager();
// if (addDebugListener)
// {
// DebugListener debug = new DebugListener(System.err, true, true, true);
// server.addBean(debug);
// deployer.addLifeCycleBinding(new DebugListenerBinding(debug));
// }
// deployer.setContexts(contexts);
// deployer.setContextAttribute(
// "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
// ".*/jetty-jakarta-servlet-api-[^/]*\\.jar$|.*/jakarta.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$");
//
// Path webappsDir = runtimeDir.resolve("webapps");
// mkdir(webappsDir);
//
// Path testWebapp = webappsDir.resolve("test.war");
// if (!Files.exists(testWebapp))
// {
// Path testWebappSrc = JettyDemos.find("demo-simple-webapp/target/demo-simple-webapp-@VER@.war");
// Files.copy(testWebappSrc, testWebapp);
// }
//
// WebAppProvider webAppProvider = new WebAppProvider();
// webAppProvider.setMonitoredDirName(webappsDir.toString());
// webAppProvider.setDefaultsDescriptor(configDir.resolve("webdefault-ee10.xml").toString());
// webAppProvider.setScanInterval(1);
// webAppProvider.setExtractWars(true);
// webAppProvider.setConfigurationManager(new PropertiesConfigurationManager());
//
// deployer.addAppProvider(webAppProvider);
// server.addBean(deployer);
//
// // === setup jetty plus ==
// Configurations.setServerDefault(server).add(new EnvConfiguration(), new PlusConfiguration(), new AnnotationConfiguration());
//
// // === jetty-stats.xml ===
// StatisticsHandler stats = new StatisticsHandler();
// stats.setHandler(server.getHandler());
// server.setHandler(stats);
// server.addBeanToAllConnectors(new ConnectionStatistics());
//
// // === Rewrite Handler
// RewriteHandler rewrite = new RewriteHandler();
// rewrite.setHandler(server.getHandler());
// server.setHandler(rewrite);
// rewrite.addRule(new InvalidURIRule());
//
// // === jetty-requestlog.xml ===
// Path logsDir = runtimeDir.resolve("logs");
// mkdir(logsDir);
// AsyncRequestLogWriter logWriter = new AsyncRequestLogWriter(logsDir.resolve("yyyy_mm_dd.request.log").toString());
// logWriter.setFilenameDateFormat("yyyy_MM_dd");
// logWriter.setRetainDays(90);
// logWriter.setTimeZone("GMT");
// CustomRequestLog requestLog = new CustomRequestLog(logWriter, CustomRequestLog.EXTENDED_NCSA_FORMAT + " \"%C\"");
// server.setRequestLog(requestLog);
//
// // === jetty-lowresources.xml ===
// LowResourceMonitor lowResourcesMonitor = new LowResourceMonitor(server);
// lowResourcesMonitor.setPeriod(1000);
// lowResourcesMonitor.setLowResourcesIdleTimeout(200);
// lowResourcesMonitor.setMonitorThreads(true);
// lowResourcesMonitor.setMaxMemory(0);
// lowResourcesMonitor.setMaxLowResourcesTime(5000);
// server.addBean(lowResourcesMonitor);
//
// // === test-realm.xml ===
// HashLoginService login = new HashLoginService();
// login.setName("Test Realm");
// login.setConfig(configDir.resolve("demo-realm.properties").toString());
// login.setHotReload(false);
// server.addBean(login);
//
// return server;
// }
//
// private static void mkdir(Path path) throws IOException
// {
// if (Files.exists(path))
// return;
// Files.createDirectories(path);
// }
//
// public static void main(String[] args) throws Exception
// {
// int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
// int securePort = ExampleUtil.getPort(args, "jetty.https.port", 8443);
// Server server = createServer(port, securePort, true);
//
// // Extra options
// server.setDumpAfterStart(true);
// server.setDumpBeforeStop(false);
// server.setStopAtShutdown(true);
//
// // Start the server
// server.start();
// server.join();
// }
public static Server createServer(int port, int securePort, boolean addDebugListener) throws Exception
{
Path configDir = Path.of("src/main/resources/demo").toAbsolutePath();
Path runtimeDir = Path.of("target/embedded/" + LikeJettyXml.class.getSimpleName()).toAbsolutePath();
mkdir(runtimeDir);
// === jetty.xml ===
// Setup Threadpool
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setMaxThreads(500);
// Server
Server server = new Server(threadPool);
// Scheduler
server.addBean(new ScheduledExecutorScheduler(null, false, -1));
// HTTP Configuration
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
httpConfig.setSecurePort(securePort);
httpConfig.setOutputBufferSize(32768);
httpConfig.setRequestHeaderSize(8192);
httpConfig.setResponseHeaderSize(8192);
httpConfig.setSendServerVersion(true);
httpConfig.setSendDateHeader(false);
// httpConfig.addCustomizer(new ForwardedRequestCustomizer());
// Handler Structure
ContextHandlerCollection contexts = new ContextHandlerCollection();
DefaultHandler defaultHandler = new DefaultHandler();
Handler.Collection handlers = new Handler.Collection();
handlers.setHandlers(contexts, defaultHandler);
server.setHandler(handlers);
// === jetty-jmx.xml ===
MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
mbContainer.beanAdded(null, LoggerFactory.getILoggerFactory());
server.addBean(mbContainer);
// === jetty-http.xml ===
ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
http.setHost("0.0.0.0");
http.setPort(port);
http.setIdleTimeout(30000);
server.addConnector(http);
// === jetty-ssl-context.xml ===
// SSL Context Factory
Path keystorePath = Paths.get("src/main/resources/etc/keystore.p12").toAbsolutePath();
if (!Files.exists(keystorePath))
throw new FileNotFoundException(keystorePath.toString());
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystorePath.toString());
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setTrustStorePath(keystorePath.toString());
sslContextFactory.setTrustStorePassword("storepwd");
// === jetty-ssl.xml ===
// SSL HTTP Configuration
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
// === jetty-https.xml ===
// SSL Connector
ServerConnector sslConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(httpsConfig));
sslConnector.setPort(securePort);
server.addConnector(sslConnector);
// === jetty-deploy.xml ===
DeploymentManager deployer = new DeploymentManager();
if (addDebugListener)
{
DebugListener debug = new DebugListener(System.err, true, true, true);
server.addBean(debug);
}
deployer.setContexts(contexts);
Path webappsDir = runtimeDir.resolve("webapps");
mkdir(webappsDir);
Path testWebapp = webappsDir.resolve("test.war");
if (!Files.exists(testWebapp))
{
Path testWebappSrc = JettyDemos.find("jetty-ee10-demo-simple-webapp/target/jetty-ee10-demo-simple-webapp-@VER@.war");
Files.copy(testWebappSrc, testWebapp);
}
// == Build ee10 Environment ==
// Support for environment specific classpath / modulepath goes here
String environmentName = "ee10";
Environment ee10 = Environment.get(environmentName);
if (ee10 == null)
{
ee10 = new EnvironmentBuilder(environmentName).build();
Environment.set(ee10);
}
// === jetty-ee10-deploy.xml ===
ee10.setAttribute("contextHandlerClass", org.eclipse.jetty.ee10.webapp.WebAppContext.class.getName());
ContextProvider webAppProvider = new ContextProvider();
webAppProvider.setEnvironmentName(environmentName);
webAppProvider.setMonitoredDirName(webappsDir.toString());
webAppProvider.setDefaultsDescriptor(configDir.resolve("webdefault-ee10.xml").toString());
webAppProvider.setScanInterval(1);
webAppProvider.setExtractWars(true);
webAppProvider.getProperties().put(Deployable.CONTAINER_SCAN_JARS,
".*/jakarta.servlet-api-[^/]*\\.jar$|.*jakarta.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$");
deployer.addAppProvider(webAppProvider);
server.addBean(deployer);
// === setup jetty plus ==
Configurations.setServerDefault(server)
.add(new EnvConfiguration(), new PlusConfiguration(), new AnnotationConfiguration());
// === jetty-stats.xml ===
StatisticsHandler stats = new StatisticsHandler();
stats.setHandler(server.getHandler());
server.setHandler(stats);
server.addBeanToAllConnectors(new ConnectionStatistics());
// === Rewrite Handler
RewriteHandler rewrite = new RewriteHandler();
rewrite.setHandler(server.getHandler());
server.setHandler(rewrite);
rewrite.addRule(new InvalidURIRule());
// === jetty-requestlog.xml ===
Path logsDir = runtimeDir.resolve("logs");
mkdir(logsDir);
AsyncRequestLogWriter logWriter = new AsyncRequestLogWriter(logsDir.resolve("yyyy_mm_dd.request.log").toString());
logWriter.setFilenameDateFormat("yyyy_MM_dd");
logWriter.setRetainDays(90);
logWriter.setTimeZone("GMT");
CustomRequestLog requestLog = new CustomRequestLog(logWriter, CustomRequestLog.EXTENDED_NCSA_FORMAT + " \"%C\"");
server.setRequestLog(requestLog);
// === jetty-lowresources.xml ===
LowResourceMonitor lowResourcesMonitor = new LowResourceMonitor(server);
lowResourcesMonitor.setPeriod(1000);
lowResourcesMonitor.setLowResourcesIdleTimeout(200);
lowResourcesMonitor.setMonitorThreads(true);
lowResourcesMonitor.setMaxMemory(0);
lowResourcesMonitor.setMaxLowResourcesTime(5000);
server.addBean(lowResourcesMonitor);
// === Realm ===
HashLoginService login = new HashLoginService();
login.setName("Test Realm");
Path realmFile = configDir.resolve("ee10-demo-realm.properties");
Resource realmResource = ResourceFactory.of(server).newResource(realmFile);
if (!Resources.isReadableFile(realmResource))
throw new FileNotFoundException("Unable to find config: " + realmFile);
login.setConfig(realmResource);
login.setHotReload(false);
server.addBean(login);
return server;
}
private static void mkdir(Path path) throws IOException
{
if (Files.exists(path))
return;
Files.createDirectories(path);
}
public static void main(String[] args) throws Exception
{
int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
int securePort = ExampleUtil.getPort(args, "jetty.https.port", 8443);
Server server = createServer(port, securePort, true);
// Extra options
server.setDumpAfterStart(true);
server.setDumpBeforeStop(false);
server.setStopAtShutdown(true);
// Start the server
server.start();
server.join();
}
}

View File

@ -29,14 +29,6 @@
This file is applied to a Web application before its own WEB_INF/web.xml file
</description>
<!-- ==================================================================== -->
<!-- Removes static references to beans from javax.el.BeanELResolver to -->
<!-- ensure webapp classloader can be released on undeploy -->
<!-- ==================================================================== -->
<listener>
<listener-class>org.eclipse.jetty.ee10.servlet.listener.ELContextCleaner</listener-class>
</listener>
<!-- ==================================================================== -->
<!-- Removes static cache of Methods from java.beans.Introspector to -->
<!-- ensure webapp classloader can be released on undeploy -->

View File

@ -23,7 +23,6 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -39,8 +38,7 @@ public class LikeJettyXmlTest extends AbstractEmbeddedTest
@BeforeEach
public void startServer() throws Exception
{
//TODO fix me
//server = LikeJettyXml.createServer(0, 0, false);
server = LikeJettyXml.createServer(0, 0, false);
server.start();
Map<String, Integer> ports = ServerUtil.fixDynamicPortConfigurations(server);
@ -57,7 +55,6 @@ public class LikeJettyXmlTest extends AbstractEmbeddedTest
LifeCycle.stop(server);
}
@Disabled //TODO
@Test
public void testGetTest() throws Exception
{
@ -74,7 +71,6 @@ public class LikeJettyXmlTest extends AbstractEmbeddedTest
assertThat("Response Content", responseBody, containsString("Hello"));
}
@Disabled //TODO
@Test
public void testGetTestSsl() throws Exception
{

View File

@ -42,7 +42,7 @@ public class ProxyServerTest extends AbstractEmbeddedTest
server.start();
URI uri = server.getURI();
client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", uri.getPort()));
client.getProxyConfiguration().addProxy(new HttpProxy("localhost", uri.getPort()));
}
@AfterEach

View File

@ -17,8 +17,8 @@ import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.ee10.webapp.Configuration;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,9 +47,9 @@ public class MavenQuickStartConfiguration extends QuickStartConfiguration
//Iterate over all of the resource bases and ignore any that were original bases, just
//deleting the overlays
Resource res = context.getBaseResource();
if (res instanceof ResourceCollection)
if (res instanceof CombinedResource)
{
for (Resource r : ((ResourceCollection)res).getResources())
for (Resource r : ((CombinedResource)res).getResources())
{
if (originalBaseStr.contains(r.toString()))
continue;

View File

@ -39,8 +39,8 @@ import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -218,7 +218,7 @@ public class MavenWebAppContext extends WebAppContext
* configuration
*
* @param resourceBases Array of resources strings to set as a
* {@link ResourceCollection}.
* {@link CombinedResource}.
*/
public void setResourceBases(String[] resourceBases)
{

View File

@ -30,8 +30,8 @@ import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.xml.XmlConfiguration;
/**
@ -106,8 +106,8 @@ public class WebAppPropertyConverter
//send over the calculated resource bases that includes unpacked overlays
Resource baseResource = webApp.getBaseResource();
if (baseResource instanceof ResourceCollection)
props.put(BASE_DIRS, toCSV(((ResourceCollection)webApp.getBaseResource()).getResources()));
if (baseResource instanceof CombinedResource)
props.put(BASE_DIRS, toCSV(((CombinedResource)webApp.getBaseResource()).getResources()));
else if (baseResource instanceof Resource)
props.put(BASE_DIRS, webApp.getBaseResource().toString());

View File

@ -16,17 +16,17 @@ package org.eclipse.jetty.ee10.maven.plugin;
import java.io.File;
import java.io.FileInputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -152,10 +152,13 @@ public class TestWebAppPropertyConverter
assertEquals(true, webApp.isPersistTempDirectory());
assertEquals(war.getAbsolutePath(), webApp.getWar());
assertEquals(webXml.getAbsolutePath(), webApp.getDescriptor());
assertThat(webApp.getBaseResource(), instanceOf(ResourceCollection.class));
assertThat(webApp.getBaseResource(), instanceOf(CombinedResource.class));
ResourceCollection resourceCollection = (ResourceCollection)webApp.getBaseResource();
List<URI> actual = resourceCollection.getResources().stream().filter(Objects::nonNull).map(Resource::getURI).toList();
Resource combinedResource = webApp.getBaseResource();
List<URI> actual = new ArrayList<>();
for (Resource r : combinedResource)
if (r != null)
actual.add(r.getURI());
URI[] expected = new URI[]{base1.toURI(), base2.toURI()};
assertThat(actual, containsInAnyOrder(expected));
}

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