Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #8886 - support extensible Resource URI schemes #8888

Merged
merged 10 commits into from
Nov 23, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import static java.nio.charset.StandardCharsets.UTF_8;
Expand Down Expand Up @@ -253,6 +254,7 @@ public void onHeaders(Stream stream, HeadersFrame frame)
}

@Test
@Tag("flaky") // see #8896
public void testTrailersSentByServerShouldNotSendEmptyDataFrame() throws Exception
{
String trailerName = "X-Trailer";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.stream.Stream;

import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -1903,7 +1904,8 @@ public static URI toURI(String resource)
Objects.requireNonNull(resource);

// Only try URI for string for known schemes, otherwise assume it is a Path
return (KNOWN_SCHEMES.getBest(resource) != null)
ResourceFactory resourceFactory = ResourceFactory.getBestByScheme(resource);
return (resourceFactory != null)
? correctFileURI(URI.create(resource))
: Paths.get(resource).toUri();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
//
// ========================================================================
// 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.util.resource;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.time.Instant;

import org.eclipse.jetty.util.FileID;

/**
* Abstract ResourceFactory for {@link java.net.URL} based resources.
*/
public abstract class AbstractUrlResourceFactory implements ResourceFactory
{
private final String supportedProtocol;
joakime marked this conversation as resolved.
Show resolved Hide resolved
private int connectTimeout;
private boolean useCaches;

protected AbstractUrlResourceFactory(String protocol)
{
this.supportedProtocol = protocol;
this.connectTimeout = Integer.parseInt(System.getProperty(this.getClass().getName() + ".connectTimeout", "1000"));
this.useCaches = Boolean.parseBoolean(System.getProperty(this.getClass().getName() + ".useCaches", "true"));
}

public int getConnectTimeout()
{
return connectTimeout;
}

public void setConnectTimeout(int connectTimeout)
{
this.connectTimeout = connectTimeout;
}

public boolean isUseCaches()
{
return useCaches;
}

public void setUseCaches(boolean useCaches)
{
this.useCaches = useCaches;
}

@Override
public Resource newResource(final URI uri)
{
if (!uri.getScheme().equalsIgnoreCase(supportedProtocol))
throw new IllegalArgumentException("Scheme not support: " + uri.getScheme());

try
{
return new URLResource(uri, this.connectTimeout, this.useCaches);
}
catch (MalformedURLException e)
{
throw new RuntimeException("Bad URI: " + uri, e);
}
}

private static class URLResource extends Resource
{
private final URI uri;
private final URL url;
private final int connectTimeout;
private final boolean useCaches;

public URLResource(URI uri, int connectTimeout, boolean useCaches) throws MalformedURLException
{
this.uri = uri;
this.url = uri.toURL();
this.connectTimeout = connectTimeout;
this.useCaches = useCaches;
}

private URLConnection newConnection() throws IOException
{
URLConnection urlConnection = url.openConnection();
urlConnection.setUseCaches(this.useCaches);
urlConnection.setConnectTimeout(this.connectTimeout);
return urlConnection;
}

@Override
public Path getPath()
{
return null;
}

@Override
public boolean isContainedIn(Resource r)
{
// compare starting URIs?
return false;
}

@Override
public boolean isDirectory()
{
return uri.getPath().endsWith("/");
}

@Override
public boolean isReadable()
{
return exists();
}

@Override
public URI getURI()
{
return uri;
}

@Override
public String getName()
{
return uri.getPath();
}

@Override
public String getFileName()
{
return FileID.getFileName(uri);
}

@Override
public Resource resolve(String subUriPath)
{
URI newURI = uri.resolve(subUriPath);
try
{
return new URLResource(newURI, this.connectTimeout, this.useCaches);
}
catch (MalformedURLException e)
{
return null;
}
}

@Override
public boolean exists()
{
try
{
newConnection();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existence could probably be cached, i.e have a Boolean that is null if never tested and set by the first call to newConnection()... but then we should not worry too much about this class being efficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this class is used for resourcebundle: based URL protocols then we don't want existence cached.
If this class is used for https: then we also don't want the existence cached.

Besides, any kind of caching on our part would just conflict with the URLConnection.setUseCaches(boolean) behaviors.

return true;
}
catch (IOException e)
{
return false;
}
}

@Override
public InputStream newInputStream() throws IOException
{
URLConnection urlConnection = newConnection();
return urlConnection.getInputStream();
}

@Override
public Instant lastModified()
{
try
{
URLConnection urlConnection = newConnection();
return Instant.ofEpochMilli(urlConnection.getLastModified());
}
catch (IOException e)
{
return Instant.EPOCH;
}
}

@Override
public long length()
{
try
{
URLConnection urlConnection = newConnection();
return urlConnection.getContentLengthLong();
}
catch (IOException e)
{
return -1;
}
}

@Override
public ReadableByteChannel newReadableByteChannel()
{
// not really possible with the URL interface
return null;
}

@Override
public boolean isAlias()
{
return false;
}

@Override
public URI getRealURI()
{
return getURI();
}

@Override
public String toString()
{
return String.format("URLResource@%X(%s)", this.uri.hashCode(), this.uri.toASCIIString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ Mount mount(URI uri) throws IOException
{
if (!uri.isAbsolute())
throw new IllegalArgumentException("not an absolute uri: " + uri);
if (PathResource.ALLOWED_SCHEMES.contains(uri.getScheme()))
throw new IllegalArgumentException("not an allowed scheme: " + uri);
if (!uri.getScheme().equalsIgnoreCase("jar"))
throw new IllegalArgumentException("not an supported scheme: " + uri);

FileSystem fileSystem = null;
try (AutoLock ignore = poolLock.lock())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.eclipse.jetty.util.URIUtil;

Expand All @@ -28,14 +26,6 @@
*/
public class MountedPathResource extends PathResource
{
public static MountedPathResource of(URI uri) throws IOException
{
Path path = Paths.get(uri.normalize());
if (!Files.exists(path))
return null;
return new MountedPathResource(path, uri);
}

private final URI containerUri;

MountedPathResource(URI uri) throws IOException
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// ========================================================================
// 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.util.resource;

import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class MountedPathResourceFactory implements ResourceFactory
{
@Override
public Resource newResource(URI uri)
{
Path path = Paths.get(uri.normalize());
if (!Files.exists(path))
return null;
return new MountedPathResource(path, uri);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,12 @@ public class PathResource extends Resource
{
private static final Logger LOG = LoggerFactory.getLogger(PathResource.class);

public static Index<String> ALLOWED_SCHEMES = new Index.Builder<String>()
public static Index<String> SUPPORTED_SCHEMES = new Index.Builder<String>()
.caseSensitive(false)
.with("file")
.with("jrt")
.build();

public static PathResource of(URI uri) throws IOException
{
Path path = Paths.get(uri.normalize());
if (!Files.exists(path))
return null;
return new PathResource(path, uri, false);
}

// The path object represented by this instance
private final Path path;
// The as-requested URI for this path object
Expand Down Expand Up @@ -176,7 +168,7 @@ public static boolean isSameName(Path pathA, Path pathB)
{
if (!uri.isAbsolute())
throw new IllegalArgumentException("not an absolute uri: " + uri);
if (!bypassAllowedSchemeCheck && !ALLOWED_SCHEMES.contains(uri.getScheme()))
if (!bypassAllowedSchemeCheck && !SUPPORTED_SCHEMES.contains(uri.getScheme()))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should review the need for bypassAllowedSchemeCheck, it probably doesn't have much purpose left

throw new IllegalArgumentException("not an allowed scheme: " + uri);

if (Files.isDirectory(path))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// ========================================================================
// 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.util.resource;

import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathResourceFactory implements ResourceFactory
{
@Override
public Resource newResource(URI uri)
{
Path path = Paths.get(uri.normalize());
if (!Files.exists(path))
return null;
return new PathResource(path, uri, false);
}
}
Loading