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

Add support for serving directory listing #329

Merged
merged 2 commits into from
Oct 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 107 additions & 17 deletions src/main/java/org/arl/fjage/connectors/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.*;

/**
* Web server instance manager.
Expand All @@ -54,8 +51,8 @@ public class WebServer {

//////// static attributes and methods

private static Map<Integer,WebServer> servers = new HashMap<Integer,WebServer>();
private static java.util.logging.Logger log = java.util.logging.Logger.getLogger(WebServer.class.getName());
private static final Map<Integer,WebServer> servers = new HashMap<Integer,WebServer>();
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(WebServer.class.getName());

static {
// disable Jetty logging (except warnings)
Expand Down Expand Up @@ -257,9 +254,9 @@ public void remove(ContextHandler handler) {
*
* @param context context path.
* @param resource resource path.
* @param cacheControl cache control header.
* @param options WebServerOptions object.
*/
public void add(String context, String resource, String cacheControl) {
public void add(String context, String resource, WebServerOptions options) {
if(resource.startsWith("/")) resource = resource.substring(1);
ArrayList<URL> res = new ArrayList<>();
try {
Expand All @@ -270,61 +267,82 @@ public void add(String context, String resource, String cacheControl) {
for (URL r : res){
String staticWebResDir = r.toExternalForm();
ContextHandler handler = new ContextHandler(context);
if (options.directoryListed) log.warning("Directory listing is not supported for resources in jars");
ResourceHandler resHandler = new ResourceHandler();
resHandler.setResourceBase(staticWebResDir);
resHandler.setWelcomeFiles(new String[]{ "index.html" });
resHandler.setDirectoriesListed(false);
resHandler.setCacheControl(cacheControl);
resHandler.setCacheControl(options.cacheControl);
resHandler.setEtags(true);
handler.setHandler(resHandler);
staticContexts.put(context, handler);
add(handler);
}
}

/**
* Adds a context to serve static documents.
*
* @param context context path.
* @param resource resource path.
* @param cacheControl cache control header.
*/
public void add(String context, String resource, String cacheControl) {
add(context, resource, new WebServerOptions().cacheControl(cacheControl));
}

/**
* Adds a context to serve static documents.
*
* @param context context path.
* @param resource resource path.
*/
public void add(String context, String resource) {
add(context, resource, "public, max-age=31536000");
add (context, resource, new WebServerOptions());
}

/**
* Adds a context to serve static documents.
*
* @param context context path.
* @param dir filesystem path of directory to serve files from.
* @param cacheControl cache control header.
* @param options WebServerOptions object.
*/
public void add(String context, File dir, String cacheControl) {
public void add(String context, File dir, WebServerOptions options) {
try {
ContextHandler handler = new ContextHandler(context);
ResourceHandler resHandler = new ResourceHandler();
ResourceHandler resHandler = options.directoryListed ? new DirectoryHandler() : new ResourceHandler();
resHandler.setResourceBase(dir.getCanonicalPath());
resHandler.setWelcomeFiles(new String[]{ "index.html" });
resHandler.setDirectoriesListed(false);
resHandler.setCacheControl(cacheControl);
resHandler.setCacheControl(options.cacheControl);
resHandler.setEtags(true);
handler.setHandler(resHandler);
staticContexts.put(context, handler);
add(handler);
}catch (IOException ex){
log.warning("Unable to find the directory : " + dir.toString());
return;
}
}

/**
* Adds a context to serve static documents.
*
* @param context context path.
* @param dir filesystem path of directory to serve files from.
* @param cacheControl cache control header.
*/
public void add(String context, File dir, String cacheControl) {
add (context, dir, new WebServerOptions().cacheControl(cacheControl));
}

/**
* Adds a context to serve static documents.
*
* @param context context path.
* @param dir filesystem path of directory to serve files from.
*/
public void add(String context, File dir) {
add(context, dir, "public, max-age=31536000");
add(context, dir, new WebServerOptions());
}

/**
Expand Down Expand Up @@ -434,4 +452,76 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
baseRequest.setHandled(true);
}
}

/**
* Builder style class for configuring web server options.
*/
public static class WebServerOptions {
protected String cacheControl = CACHE;
protected boolean directoryListed = false;

public WebServerOptions() {}

public WebServerOptions cacheControl(String cacheControl) {
this.cacheControl = cacheControl;
return this;
}

public WebServerOptions directoryListed(boolean directoryListed) {
this.directoryListed = directoryListed;
return this;
}
}

/**
* Context handler for serving Directory listing as plain text
* instead of HTML. If the request is for a directory, and the content-type
* is text/plain, the directory listing is returned as plain text, else the default
* ResourceHandler is used.
*/
private static class DirectoryHandler extends ResourceHandler {

@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (baseRequest.isHandled()) return;
File base = getBaseResource().getFile();
if (base != null && target.endsWith("/")) {
if (request.getContentType() != null && request.getContentType().equals("text/plain")) {
String path = request.getPathInfo();
if (path == null) path = "/";
File dir = new File(base, path);
if (dir.isDirectory()) {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_OK);
for (File f: dir.listFiles()) {
if (f.isHidden()) continue;
response.getWriter().println(f.getName()+" "+f.length()+" "+f.lastModified());
}
baseRequest.setHandled(true);
return;
}
} else if (request.getContentType() != null && request.getContentType().equals("application/json")) {
String path = request.getPathInfo();
if (path == null) path = "/";
File dir = new File(getBaseResource().getFile(), path);
if (dir.isDirectory()) {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().print("[");
boolean first = true;
for (File f: dir.listFiles()) {
if (f.isHidden()) continue;
if (!first) response.getWriter().print(",");
response.getWriter().print("{\"name\":\""+f.getName()+"\",\"size\":"+f.length()+",\"date\":"+f.lastModified()+"}");
first = false;
}
response.getWriter().print("]");
baseRequest.setHandled(true);
return;
}
}
}
super.handle(target, baseRequest, request, response);
}
}
}
Loading