Support for directory listing of ResourceCollections
This is a counter to #8427 to show that Resource.listing is still needed
This commit is contained in:
parent
9e745f7fdb
commit
e0a9c21615
|
@ -13,24 +13,18 @@
|
|||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.eclipse.jetty.util.resource.PathCollators;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceCollators;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -58,26 +52,8 @@ public class ResourceListing
|
|||
base = URIUtil.normalizePath(base);
|
||||
if (base == null || !resource.isDirectory())
|
||||
return null;
|
||||
Path path = resource.getPath();
|
||||
if (path == null) // Should never happen, as new Resource contract is that all Resources are a Path.
|
||||
return null;
|
||||
|
||||
List<Path> listing = null;
|
||||
try (Stream<Path> listStream = Files.list(resource.getPath()))
|
||||
{
|
||||
|
||||
listing = listStream.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Unable to get Directory Listing for: {}", resource, e);
|
||||
}
|
||||
|
||||
if (listing == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
List<Resource> listing = new ArrayList<>(resource.list().stream().map(URIUtil::encodePath).map(resource::resolve).toList());
|
||||
|
||||
boolean sortOrderAscending = true;
|
||||
String sortColumn = "N"; // name (or "M" for Last Modified, or "S" for Size)
|
||||
|
@ -108,18 +84,13 @@ public class ResourceListing
|
|||
}
|
||||
|
||||
// Perform sort
|
||||
if (sortColumn.equals("M"))
|
||||
Comparator<? super Resource> sort = switch (sortColumn)
|
||||
{
|
||||
listing.sort(PathCollators.byLastModified(sortOrderAscending));
|
||||
}
|
||||
else if (sortColumn.equals("S"))
|
||||
{
|
||||
listing.sort(PathCollators.bySize(sortOrderAscending));
|
||||
}
|
||||
else
|
||||
{
|
||||
listing.sort(PathCollators.byName(sortOrderAscending));
|
||||
}
|
||||
case "M" -> ResourceCollators.byLastModified(sortOrderAscending);
|
||||
case "S" -> ResourceCollators.bySize(sortOrderAscending);
|
||||
default -> ResourceCollators.byName(sortOrderAscending);
|
||||
};
|
||||
listing.sort(sort);
|
||||
|
||||
String decodedBase = URIUtil.decodePath(base);
|
||||
String title = "Directory: " + deTag(decodedBase);
|
||||
|
@ -229,30 +200,22 @@ public class ResourceListing
|
|||
|
||||
DateFormat dfmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
|
||||
|
||||
for (Path item : listing)
|
||||
for (Resource item : listing)
|
||||
{
|
||||
Path fileName = item.getFileName();
|
||||
if (fileName == null)
|
||||
{
|
||||
continue; // skip
|
||||
}
|
||||
|
||||
String name = fileName.toString();
|
||||
// TODO this feels fragile, as collections probably should not return a Path here
|
||||
// and even if they do, it might not be named correctly
|
||||
String name = item.getPath().toFile().getName();
|
||||
if (StringUtil.isBlank(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
continue;
|
||||
|
||||
if (Files.isDirectory(item))
|
||||
{
|
||||
if (item.isDirectory() && !name.endsWith("/"))
|
||||
name += URIUtil.SLASH;
|
||||
}
|
||||
|
||||
// Name
|
||||
buf.append("<tr><td class=\"name\"><a href=\"");
|
||||
|
||||
String href = URIUtil.addEncodedPaths(encodedBase, URIUtil.encodePath(name));
|
||||
buf.append(href);
|
||||
// TODO should this be a relative link?
|
||||
String path = URIUtil.addEncodedPaths(encodedBase, URIUtil.encodePath(name));
|
||||
buf.append(path);
|
||||
buf.append("\">");
|
||||
buf.append(deTag(name));
|
||||
buf.append(" ");
|
||||
|
@ -260,35 +223,21 @@ public class ResourceListing
|
|||
|
||||
// Last Modified
|
||||
buf.append("<td class=\"lastmodified\">");
|
||||
|
||||
try
|
||||
{
|
||||
FileTime lastModified = Files.getLastModifiedTime(item, LinkOption.NOFOLLOW_LINKS);
|
||||
buf.append(dfmt.format(new Date(lastModified.toMillis())));
|
||||
}
|
||||
catch (IOException ignore)
|
||||
{
|
||||
// do nothing (lastModifiedTime not supported by this file system)
|
||||
}
|
||||
long lastModified = item.lastModified();
|
||||
if (lastModified > 0)
|
||||
buf.append(dfmt.format(new Date(item.lastModified())));
|
||||
buf.append(" </td>");
|
||||
|
||||
// Size
|
||||
buf.append("<td class=\"size\">");
|
||||
|
||||
try
|
||||
long length = item.length();
|
||||
if (length >= 0)
|
||||
{
|
||||
long length = Files.size(item);
|
||||
if (length >= 0)
|
||||
{
|
||||
buf.append(String.format("%,d bytes", length));
|
||||
}
|
||||
}
|
||||
catch (IOException ignore)
|
||||
{
|
||||
// do nothing (size not supported by this file system)
|
||||
buf.append(String.format("%,d bytes", item.length()));
|
||||
}
|
||||
buf.append(" </td></tr>\n");
|
||||
}
|
||||
|
||||
buf.append("</tbody>\n");
|
||||
buf.append("</table>\n");
|
||||
buf.append("</body></html>\n");
|
||||
|
|
|
@ -216,6 +216,8 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
*/
|
||||
public void setBaseResource(Resource base)
|
||||
{
|
||||
if (isStarted())
|
||||
throw new IllegalStateException(getState());
|
||||
_resourceBase = base;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.eclipse.jetty.util.IO;
|
|||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.resource.FileSystemPool;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -1771,6 +1772,153 @@ public class ResourceHandlerTest
|
|||
assertThat(response.get(LOCATION), endsWith("/context/directory/;JSESSIONID=12345678?name=value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectory() throws Exception
|
||||
{
|
||||
copySimpleTestResource(docRoot);
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
String content = response.getContent();
|
||||
assertThat(content, containsString("<link href=\"jetty-dir.css\" rel=\"stylesheet\" />"));
|
||||
assertThat(content, containsString("Directory: /context"));
|
||||
assertThat(content, containsString("/context/big.txt")); // TODO should these be relative links?
|
||||
assertThat(content, containsString("/context/simple.txt"));
|
||||
assertThat(content, containsString("/context/directory/"));
|
||||
|
||||
response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/jetty-dir.css HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
// TODO fix this!
|
||||
// assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryOfCollection() throws Exception
|
||||
{
|
||||
copySimpleTestResource(docRoot);
|
||||
_rootResourceHandler.stop();
|
||||
_rootResourceHandler.setBaseResource(Resource.combine(
|
||||
ResourceFactory.root().newResource(MavenTestingUtils.getTestResourcePathDir("layer0/")),
|
||||
_rootResourceHandler.getResourceBase()));
|
||||
_rootResourceHandler.start();
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/other/ HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
|
||||
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
String content = response.getContent();
|
||||
assertThat(content, containsString("<link href=\"jetty-dir.css\" rel=\"stylesheet\" />"));
|
||||
assertThat(content, containsString("Directory: /context/other"));
|
||||
assertThat(content, containsString("/context/other/data.txt"));
|
||||
|
||||
response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/other/jetty-dir.css HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
|
||||
// TODO fix this!
|
||||
// assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
|
||||
response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/double/ HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
|
||||
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
content = response.getContent();
|
||||
assertThat(content, containsString("<link href=\"jetty-dir.css\" rel=\"stylesheet\" />"));
|
||||
assertThat(content, containsString("Directory: /context/double"));
|
||||
assertThat(content, containsString("/context/double/zero.txt"));
|
||||
|
||||
response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/double/jetty-dir.css HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
|
||||
// TODO fix this!
|
||||
// assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryOfCollections() throws Exception
|
||||
{
|
||||
copySimpleTestResource(docRoot);
|
||||
_rootResourceHandler.stop();
|
||||
_rootResourceHandler.setBaseResource(Resource.combine(
|
||||
ResourceFactory.root().newResource(MavenTestingUtils.getTestResourcePathDir("layer0/")),
|
||||
ResourceFactory.root().newResource(MavenTestingUtils.getTestResourcePathDir("layer1/")),
|
||||
_rootResourceHandler.getResourceBase()));
|
||||
_rootResourceHandler.start();
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
String content = response.getContent();
|
||||
assertThat(content, containsString("<link href=\"jetty-dir.css\" rel=\"stylesheet\" />"));
|
||||
assertThat(content, containsString("Directory: /context"));
|
||||
assertThat(content, containsString("/context/big.txt")); // TODO should these be relative links?
|
||||
assertThat(content, containsString("/context/simple.txt"));
|
||||
assertThat(content, containsString("/context/directory/"));
|
||||
assertThat(content, containsString("/context/other/"));
|
||||
assertThat(content, containsString("/context/double/"));
|
||||
|
||||
response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/double/ HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
|
||||
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
content = response.getContent();
|
||||
assertThat(content, containsString("<link href=\"jetty-dir.css\" rel=\"stylesheet\" />"));
|
||||
assertThat(content, containsString("Directory: /context/double"));
|
||||
assertThat(content, containsString("/context/double/zero.txt"));
|
||||
assertThat(content, containsString("/context/double/one.txt"));
|
||||
|
||||
response = HttpTester.parseResponse(
|
||||
_local.getResponse("""
|
||||
GET /context/double/jetty-dir.css HTTP/1.1\r
|
||||
Host: local\r
|
||||
Connection: close\r
|
||||
\r
|
||||
"""));
|
||||
|
||||
// TODO fix this!
|
||||
// assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEtagIfMatchAlwaysFailsDueToWeakEtag() throws Exception
|
||||
{
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
From layer zero
|
|
@ -0,0 +1 @@
|
|||
some data
|
|
@ -0,0 +1 @@
|
|||
From layer 1
|
Loading…
Reference in New Issue