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

Adding functionality to serve an agent's store directory #328

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
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
67 changes: 67 additions & 0 deletions src/main/java/org/arl/fjage/Agent.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.arl.fjage;

import java.io.File;
import java.io.Serializable;
import java.util.*;
import java.util.TimerTask;
Expand All @@ -20,7 +21,10 @@
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.arl.fjage.connectors.WebServer;
import org.arl.fjage.persistence.Store;
import org.arl.fjage.remote.MasterContainer;
import org.arl.fjage.remote.SlaveContainer;

/**
Expand Down Expand Up @@ -696,6 +700,42 @@ public Store getStore() {
return Store.getInstance(this);
}

/**
* Serve the Agent's store over HTTP.
*
* @param directoryListing true to enable directory listing, false otherwise
* @return true if successful, false otherwise
*/
public boolean enableServeStore(boolean directoryListing){
WebServer webServer = getWebServer();
if (webServer != null) {
String path = "store/" + this.getClass().getCanonicalName().replace(".", "/");
if (webServer.hasContext(path)) return false;
webServer.add("/"+path, new File(path+"/"), new WebServer.WebServerOptions().directoryListed(directoryListing));
return true;
}
log.warning("No web server found");
return false;
}

/**
* Stop serving the Agent's store over HTTP.
*
* @return true if successful, false otherwise
*/
public boolean disableServeStore(){
WebServer webServer = getWebServer();
if (webServer != null) {
String path = "store/" + this.getClass().getCanonicalName().replace(".", "/");
if (!webServer.hasContext(path)) return false;
webServer.remove("/"+path);
return true;
}
log.warning("No web server found");
return false;
}


/**
* Deep clones an object. This is typically used to explicitly clone a message for
* modification when autocloning is not enabled.
Expand Down Expand Up @@ -845,6 +885,33 @@ public final void run() {
platform = null;
}

/**
* Finds the web server that provides the websocket connection for the agent's container
*
* @return the web server that provides the websocket connection for the container
*/
WebServer getWebServer() {
if (container instanceof MasterContainer) {
// look for connector that starts with ws:// and get it's port
// ((MasterContainer) container).getConnectors()
for (String connector : ((MasterContainer) container).getConnectors()) {
if (connector.startsWith("ws://")) {
// Parse a string like this and find the port "ws://127.0.0.1:8080/ws"
String[] parts = connector.split(":");
if (parts.length > 2) {
try {
int port = Integer.parseInt(parts[2].split("/")[0]);
return WebServer.getInstance(port);
} catch (NumberFormatException e) {
log.warning("Invalid port number in connector: "+connector);
}
}
}
}
}
return null;
}

private class InternalRequestSender
implements RequestSender {

Expand Down
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