From 90ff4d14920f00dd28135ee115d8f88809e5812d Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Mon, 6 Sep 2010 03:53:43 +0200 Subject: [PATCH 01/16] Added some minor HTML utilities. --- src/dolda/jsvc/next/DocBuffer.java | 12 ++++++++++++ src/dolda/jsvc/next/Html.java | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/dolda/jsvc/next/DocBuffer.java b/src/dolda/jsvc/next/DocBuffer.java index 2690309..d885a0b 100644 --- a/src/dolda/jsvc/next/DocBuffer.java +++ b/src/dolda/jsvc/next/DocBuffer.java @@ -76,6 +76,18 @@ public class DocBuffer { return(doc.createTextNode(text)); } + public Node asnode(Object o) { + if(o instanceof Node) { + Node n = (Node)o; + if(n.getOwnerDocument() != doc) + return(doc.importNode(n, true)); + return(n); + } + if(o instanceof String) + return(text((String)o)); + throw(new RuntimeException("Cannot convert a " + o.getClass().getName() + " to a DOM node")); + } + public void finalise() { Node n = doc; while(true) { diff --git a/src/dolda/jsvc/next/Html.java b/src/dolda/jsvc/next/Html.java index 978cad2..5984e96 100644 --- a/src/dolda/jsvc/next/Html.java +++ b/src/dolda/jsvc/next/Html.java @@ -32,6 +32,19 @@ public class Html extends DocBuffer { return(el(ns, name, contents, attrs)); } + public Element table(Object... headers) { + Element tbl = el("table", null); + tbl.appendChild(tr(true, headers)); + return(tbl); + } + + public Element tr(boolean head, Object... cells) { + Element tr = el("tr", null); + for(Object cell : cells) + tr.appendChild(el(head?"th":"td", asnode(cell))); + return(tr); + } + public Element csslink(String href, String name) { Element el = el("link", null, "rel=stylesheet", "type=text/css"); if(name != null) -- 2.11.0 From b560fc1c45ee31c6d509781b53d5934121990189 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Mon, 6 Sep 2010 04:13:21 +0200 Subject: [PATCH 02/16] Added the includeantruntime attribute to build.xml. --- build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.xml b/build.xml index eda27f3..ff36849 100644 --- a/build.xml +++ b/build.xml @@ -17,7 +17,7 @@ - + -- 2.11.0 From 13e578b10b388cc0dea88e05b79265c21416e3a0 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Tue, 7 Sep 2010 06:54:24 +0200 Subject: [PATCH 03/16] Added initial SCGI server and a handler for serving JARs from the filesystem. There's quite some amount uglyness involved, but nothing that cannot be fixed over time. --- src/dolda/jsvc/ServerContext.java | 1 + src/dolda/jsvc/ThreadContext.java | 29 ++- src/dolda/jsvc/j2ee/J2eeContext.java | 4 + src/dolda/jsvc/scgi/DSContext.java | 48 +++++ src/dolda/jsvc/scgi/DirServer.java | 103 +++++++++++ src/dolda/jsvc/scgi/InvalidRequestException.java | 7 + src/dolda/jsvc/scgi/LimitInputStream.java | 65 +++++++ src/dolda/jsvc/scgi/ScgiReqThread.java | 26 +++ src/dolda/jsvc/scgi/ScgiRequest.java | 217 +++++++++++++++++++++++ src/dolda/jsvc/scgi/Server.java | 111 ++++++++++++ src/dolda/jsvc/util/JarContext.java | 93 ++++++++++ src/dolda/jsvc/util/Misc.java | 1 + src/dolda/jsvc/util/ResponseBuffer.java | 4 +- 13 files changed, 706 insertions(+), 3 deletions(-) create mode 100644 src/dolda/jsvc/scgi/DSContext.java create mode 100644 src/dolda/jsvc/scgi/DirServer.java create mode 100644 src/dolda/jsvc/scgi/InvalidRequestException.java create mode 100644 src/dolda/jsvc/scgi/LimitInputStream.java create mode 100644 src/dolda/jsvc/scgi/ScgiReqThread.java create mode 100644 src/dolda/jsvc/scgi/ScgiRequest.java create mode 100644 src/dolda/jsvc/scgi/Server.java create mode 100644 src/dolda/jsvc/util/JarContext.java diff --git a/src/dolda/jsvc/ServerContext.java b/src/dolda/jsvc/ServerContext.java index 423deba..47d4d13 100644 --- a/src/dolda/jsvc/ServerContext.java +++ b/src/dolda/jsvc/ServerContext.java @@ -5,4 +5,5 @@ public interface ServerContext { public String sysconfig(String key, String def); public String libconfig(String key, String def); public String name(); + public RequestThread worker(Responder root, Request req, ThreadGroup tg, String name); } diff --git a/src/dolda/jsvc/ThreadContext.java b/src/dolda/jsvc/ThreadContext.java index 27d90bf..d0e32fd 100644 --- a/src/dolda/jsvc/ThreadContext.java +++ b/src/dolda/jsvc/ThreadContext.java @@ -137,7 +137,7 @@ public class ThreadContext extends ThreadGroup { } public RequestThread respond(Request req) { - return(new RequestThread(root, req, workers, "Worker thread " + reqs++)); + return(ctx.worker(root, req, workers, "Worker thread " + reqs++)); } private Responder bootstrap(final Class bootclass) { @@ -190,4 +190,31 @@ public class ThreadContext extends ThreadGroup { } return(null); } + + public static class CreateException extends Exception { + public CreateException(String message) { + super(message); + } + + public CreateException(String message, Throwable cause) { + super(message, cause); + } + } + + public static ThreadContext create(ServerContext ctx, ClassLoader cl) throws CreateException { + String nm = "JSvc Service"; + if(ctx.name() != null) + nm = "JSvc Service for " + ctx.name(); + + String clnm = ctx.libconfig("jsvc.bootstrap", null); + if(clnm == null) + throw(new CreateException("No JSvc bootstrapper specified")); + Class bc; + try { + bc = cl.loadClass(clnm); + } catch(ClassNotFoundException e) { + throw(new CreateException("Invalid JSvc bootstrapper specified", e)); + } + return(new ThreadContext(null, nm, ctx, bc)); + } } diff --git a/src/dolda/jsvc/j2ee/J2eeContext.java b/src/dolda/jsvc/j2ee/J2eeContext.java index 84ce918..f687afd 100644 --- a/src/dolda/jsvc/j2ee/J2eeContext.java +++ b/src/dolda/jsvc/j2ee/J2eeContext.java @@ -43,4 +43,8 @@ public abstract class J2eeContext implements ServerContext { public ServletConfig j2eeconfig() { return(sc); } + + public RequestThread worker(Responder root, Request req, ThreadGroup tg, String name) { + return(new RequestThread(root, req, tg, name)); + } } diff --git a/src/dolda/jsvc/scgi/DSContext.java b/src/dolda/jsvc/scgi/DSContext.java new file mode 100644 index 0000000..9103525 --- /dev/null +++ b/src/dolda/jsvc/scgi/DSContext.java @@ -0,0 +1,48 @@ +package dolda.jsvc.scgi; + +import java.io.*; +import dolda.jsvc.*; +import dolda.jsvc.util.*; + +public class DSContext extends JarContext { + public final long mtime; + private final File datroot; + public final ThreadContext tg; + + public DSContext(File jar, File datroot) throws ThreadContext.CreateException { + super(jar); + this.mtime = jar.lastModified(); + this.datroot = datroot; + loadconfig(); + this.tg = ThreadContext.create(this, loader); + } + + private void loadconfig() { + if(datroot != null) { + File sroot = new File(new File(datroot, "store"), name()); + sysconfig.put("jsvc.storage", "file:" + sroot.getPath()); + File conf = new File(datroot, "jsvc.properties"); + if(conf.exists()) { + try { + InputStream in = new FileInputStream(conf); + try { + sysconfig.load(in); + } finally { + in.close(); + } + } catch(IOException e) { + throw(new RuntimeException(e)); + } + } + } + } + + public RequestThread worker(Responder root, Request req, ThreadGroup tg, String name) { + java.net.Socket sk = ((ScgiRequest)req).sk; + if(req.path().equals("")) { + return(new ScgiReqThread(new RootRedirect(""), req, tg, name, sk)); + } else { + return(new ScgiReqThread(root, RequestWrap.chpath(req, req.path().substring(1)), tg, name, sk)); + } + } +} diff --git a/src/dolda/jsvc/scgi/DirServer.java b/src/dolda/jsvc/scgi/DirServer.java new file mode 100644 index 0000000..7bf064d --- /dev/null +++ b/src/dolda/jsvc/scgi/DirServer.java @@ -0,0 +1,103 @@ +package dolda.jsvc.scgi; + +import java.io.*; +import java.net.*; +import java.util.*; +import dolda.jsvc.*; +import dolda.jsvc.util.*; +import dolda.jsvc.j2ee.PosixArgs; + +public class DirServer extends Server { + private final Map contexts = new HashMap(); + private final File datroot; + + public DirServer(ServerSocket sk, File datroot) { + super(sk); + this.datroot = datroot; + } + + private DSContext context(File file) throws ThreadContext.CreateException { + synchronized(contexts) { + DSContext ctx = contexts.get(file); + if(ctx != null) { + if(ctx.mtime < file.lastModified()) { + ctx.tg.destroy(); + contexts.remove(file); + ctx = null; + } + } + if(ctx == null) { + ctx = new DSContext(file, datroot); + contexts.put(file, ctx); + } + return(ctx); + } + } + + public void handle(Map head, Socket sk) throws Exception { + String filename = head.get("SCRIPT_FILENAME"); + if(filename == null) + throw(new Exception("Request for DirServer must contain SCRIPT_FILENAME")); + File file = new File(filename); + if(!file.exists() || !file.canRead()) + throw(new Exception("Cannot access the requested JSvc file " + file.toString())); + DSContext ctx = context(file); + Request req = new ScgiRequest(sk, head); + RequestThread w = ctx.tg.respond(req); + w.start(); + } + + private static void usage(PrintStream out) { + out.println("usage: dolda.jsvc.scgi.DirServer [-h] [-e CHARSET] [-d DATADIR] PORT"); + } + + public static void main(String[] args) { + PosixArgs opt = PosixArgs.getopt(args, "h"); + if(opt == null) { + usage(System.err); + System.exit(1); + } + String charset = null; + File datroot = null; + for(char c : opt.parsed()) { + switch(c) { + case 'e': + charset = opt.arg; + break; + case 'd': + datroot = new File(opt.arg); + if(!datroot.exists() || !datroot.isDirectory()) { + System.err.println(opt.arg + ": no such directory"); + System.exit(1); + } + break; + case 'h': + usage(System.out); + return; + } + } + if(opt.rest.length < 1) { + usage(System.err); + System.exit(1); + } + if(datroot == null) { + datroot = new File(System.getProperty("user.home"), ".jsvc"); + if(!datroot.exists() || !datroot.isDirectory()) + datroot = null; + } + int port = Integer.parseInt(opt.rest[0]); + ServerSocket sk; + try { + sk = new ServerSocket(port); + } catch(IOException e) { + System.err.println("could not bind to port " + port + ": " + e.getMessage()); + System.exit(1); + return; /* Because javac is stupid. :-/ */ + } + DirServer s = new DirServer(sk, datroot); + if(charset != null) + s.headcs = charset; + + new Thread(s, "SCGI server thread").start(); + } +} diff --git a/src/dolda/jsvc/scgi/InvalidRequestException.java b/src/dolda/jsvc/scgi/InvalidRequestException.java new file mode 100644 index 0000000..bb8f965 --- /dev/null +++ b/src/dolda/jsvc/scgi/InvalidRequestException.java @@ -0,0 +1,7 @@ +package dolda.jsvc.scgi; + +public class InvalidRequestException extends java.io.IOException { + public InvalidRequestException(String message) { + super(message); + } +} diff --git a/src/dolda/jsvc/scgi/LimitInputStream.java b/src/dolda/jsvc/scgi/LimitInputStream.java new file mode 100644 index 0000000..7a37f25 --- /dev/null +++ b/src/dolda/jsvc/scgi/LimitInputStream.java @@ -0,0 +1,65 @@ +package dolda.jsvc.scgi; + +import java.io.*; + +public class LimitInputStream extends InputStream { + private final InputStream bk; + private final long limit; + private long read; + + public LimitInputStream(InputStream bk, long limit) { + this.bk = bk; + this.limit = limit; + } + + public void close() throws IOException { + bk.close(); + } + + public int available() throws IOException { + int av = bk.available(); + synchronized(this) { + if(av > limit - read) + av = (int)(limit - read); + return(av); + } + } + + public int read() throws IOException { + synchronized(this) { + if(read >= limit) + return(-1); + int ret = bk.read(); + if(ret >= 0) + read++; + return(ret); + } + } + + public int read(byte[] b) throws IOException { + return(read(b, 0, b.length)); + } + + public int read(byte[] b, int off, int len) throws IOException { + synchronized(this) { + if(read >= limit) + return(-1); + if(len > limit - read) + len = (int)(limit - read); + int ret = bk.read(b, off, len); + if(ret > 0) + read += ret; + return(ret); + } + } + + public long skip(long n) throws IOException { + synchronized(this) { + if(n > limit - read) + n = limit - read; + long ret = bk.skip(n); + read += ret; + return(ret); + } + } +} diff --git a/src/dolda/jsvc/scgi/ScgiReqThread.java b/src/dolda/jsvc/scgi/ScgiReqThread.java new file mode 100644 index 0000000..1b1ad22 --- /dev/null +++ b/src/dolda/jsvc/scgi/ScgiReqThread.java @@ -0,0 +1,26 @@ +package dolda.jsvc.scgi; + +import java.io.*; +import java.net.*; +import dolda.jsvc.*; + +public class ScgiReqThread extends RequestThread { + protected final Socket sk; + + public ScgiReqThread(Responder root, Request req, ThreadGroup tg, String name, Socket sk) { + super(root, req, tg, name); + this.sk = sk; + } + + public void run() { + try { + super.run(); + } finally { + try { + sk.close(); + } catch(IOException e) { + throw(new RuntimeException(e)); + } + } + } +} diff --git a/src/dolda/jsvc/scgi/ScgiRequest.java b/src/dolda/jsvc/scgi/ScgiRequest.java new file mode 100644 index 0000000..192d380 --- /dev/null +++ b/src/dolda/jsvc/scgi/ScgiRequest.java @@ -0,0 +1,217 @@ +package dolda.jsvc.scgi; + +import java.io.*; +import java.net.*; +import java.util.*; +import dolda.jsvc.*; +import dolda.jsvc.util.*; + +public class ScgiRequest extends ResponseBuffer { + final Socket sk; + private final Map environ; + private final InputStream in; + private final String method, path; + private final URL url, context; + private MultiMap params = null; + private MultiMap inhead = new HeaderTreeMap(); + + public ScgiRequest(Socket sk, Map environ) throws IOException { + this.sk = sk; + this.environ = environ; + for(Map.Entry var : environ.entrySet()) { + String k = var.getKey(); + if((k.length() > 5) && k.substring(0, 5).equals("HTTP_")) { + StringBuilder buf = new StringBuilder(); + boolean f = true; + for(int i = 5; i < k.length(); i++) { + char c = k.charAt(i); + if(c == '_') { + buf.append('-'); + f = true; + } else if(f) { + buf.append(Character.toUpperCase(c)); + f = false; + } else { + buf.append(Character.toLowerCase(c)); + } + } + inhead.add(buf.toString(), var.getValue()); + } + } + long len; + { + String h = environ.get("CONTENT_LENGTH"); + if(h == null) { + len = 0; + } else { + try { + len = Long.parseLong(h); + } catch(NumberFormatException e) { + throw(new InvalidRequestException("Invalid Content-Length header: " + h)); + } + } + } + this.in = new LimitInputStream(sk.getInputStream(), len); + path = environ.get("PATH_INFO"); + if(path == null) + throw(new InvalidRequestException("Missing PATH_INFO")); + method = environ.get("REQUEST_METHOD"); + if(method == null) + throw(new InvalidRequestException("Missing REQUEST_METHOD")); + { + /* Ewwww, this is disgusting! */ + String scheme = "http"; + if(environ.get("HTTPS") != null) + scheme = "https"; + int port = -1; + String host = environ.get("HTTP_HOST"); + if((host == null) || (host.length() < 1)) { + if((host = environ.get("SERVER_NAME")) == null) + throw(new InvalidRequestException("Both HTTP_HOST and SERVER name are missing")); + String portnum = environ.get("SERVER_PORT"); + if(portnum == null) + throw(new InvalidRequestException("Missing SERVER_PORT")); + try { + port = Integer.parseInt(portnum); + } catch(NumberFormatException e) { + throw(new InvalidRequestException("Bad SERVER_PORT: " + portnum)); + } + if((port == 80) && scheme.equals("http")) + port = -1; + else if((port == 443) && scheme.equals("https")) + port = -1; + } else { + int p; + if((host.charAt(0) == '[') && ((p = host.indexOf(']', 1)) > 1)) { + String newhost = host.substring(1, p); + if((p = host.indexOf(':', p + 1)) >= 0) { + try { + port = Integer.parseInt(host.substring(p + 1)); + } catch(NumberFormatException e) {} + } + host = newhost; + } else if((p = host.indexOf(':')) >= 0) { + try { + port = Integer.parseInt(host.substring(p + 1)); + host = host.substring(0, p); + } catch(NumberFormatException e) {} + } + } + String nm = environ.get("SCRIPT_NAME"); + if(nm == null) + throw(new InvalidRequestException("Missing SCRIPT_NAME")); + String q = environ.get("QUERY_STRING"); + if(q != null) + q = "?" + q; + else + q = ""; + try { + url = new URL(scheme, host, port, nm + path + q); + if(nm.charAt(nm.length() - 1) != '/') + nm += "/"; /* XXX? */ + context = new URL(scheme, host, port, nm); + } catch(MalformedURLException e) { + throw(new Error(e)); + } + } + } + + public MultiMap inheaders() { + return(inhead); + } + + public ServerContext ctx() { + return(ThreadContext.current().server()); + } + + public InputStream input() { + return(in); + } + + public URL url() { + return(url); + } + + public URL rooturl() { + return(context); + } + + public String path() { + return(path); + } + + public String method() { + return(method); + } + + public MultiMap params() { + if(params == null) + params = Params.stdparams(this); + return(params); + } + + public SocketAddress localaddr() { + String portnum = environ.get("SERVER_PORT"); + int port = -1; + try { + if(portnum != null) + port = Integer.parseInt(portnum); + } catch(NumberFormatException e) {} + if(port < 0) + return(null); /* XXX? */ + String addr; + addr = environ.get("X_ASH_SERVER_ADDRESS"); + if(addr == null) + return(new InetSocketAddress(port)); /* XXX? */ + else + return(new InetSocketAddress(addr, port)); + } + + public SocketAddress remoteaddr() { + String addr; + String portnum; + addr = environ.get("REMOTE_ADDR"); + portnum = environ.get("X_ASH_PORT"); + int port = -1; + try { + if(portnum != null) + port = Integer.parseInt(portnum); + } catch(NumberFormatException e) {} + if((addr != null) && (port >= 0)) + return(new InetSocketAddress(addr, port)); + return(null); /* XXX? */ + } + + private void checkstring(String s) { + for(int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if((c < 32) || (c >= 128)) + throw(new RuntimeException("Invalid header string: " + s)); + } + } + + protected void backflush() throws IOException { + Writer out = new OutputStreamWriter(realoutput(), Misc.ascii); + out.write(String.format("Status: %d %s\n", respcode, resptext)); + for(Map.Entry e : outheaders().entrySet()) { + String k = e.getKey(); + String v = e.getValue(); + checkstring(k); + checkstring(v); + out.write(String.format("%s: %s\n", k, v)); + } + out.write("\n"); + out.flush(); + } + + protected OutputStream realoutput() { + try { + return(sk.getOutputStream()); + } catch(IOException e) { + /* It is not obvious why this would happen, so I'll wait + * until I know whatever might happen to try and implement + * meaningful behavior. */ + throw(new RuntimeException(e)); + } + } +} diff --git a/src/dolda/jsvc/scgi/Server.java b/src/dolda/jsvc/scgi/Server.java new file mode 100644 index 0000000..f01eae9 --- /dev/null +++ b/src/dolda/jsvc/scgi/Server.java @@ -0,0 +1,111 @@ +package dolda.jsvc.scgi; + +import java.util.logging.*; +import java.io.*; +import java.net.*; +import java.util.*; + +public abstract class Server implements Runnable { + private final ServerSocket sk; + private final Logger logger = Logger.getLogger("dolda.jsvc.scgi"); + public String headcs = "UTF-8"; + + public Server(ServerSocket sk) { + this.sk = sk; + } + + private static int readnslen(InputStream in) throws IOException { + int ret = 0; + while(true) { + int c = in.read(); + if(c == ':') + return(ret); + else if((c >= '0') && (c <= '9')) + ret = (ret * 10) + (c - '0'); + else + throw(new InvalidRequestException("Malformed netstring length")); + } + } + + private static byte[] readns(InputStream in) throws IOException { + byte[] buf = new byte[readnslen(in)]; + int off = 0; + while(off < buf.length) { + int ret = in.read(buf, off, buf.length - off); + if(ret < 0) + throw(new InvalidRequestException("Unexpected EOS in netstring")); + off += ret; + } + if(in.read() != ',') + throw(new InvalidRequestException("Unterminated netstring")); + return(buf); + } + + private Map readhead(InputStream in) throws IOException { + byte[] rawhead = readns(in); + String head = new String(rawhead, headcs); + Map ret = new HashMap(); + int p = 0; + while(true) { + int p2 = head.indexOf(0, p); + if(p2 < 0) { + if(p == head.length()) + return(ret); + throw(new InvalidRequestException("Malformed headers")); + } + String key = head.substring(p, p2); + int p3 = head.indexOf(0, p2 + 1); + if(p3 < 0) + throw(new InvalidRequestException("Malformed headers")); + String val = head.substring(p2 + 1, p3); + ret.put(key, val); + p = p3 + 1; + } + } + + private boolean checkhead(Map head) { + if(!head.containsKey("SCGI") || !head.get("SCGI").equals("1")) + return(false); + return(true); + } + + protected abstract void handle(Map head, Socket sk) throws Exception; + + private void serve(Socket sk) { + try { + try { + InputStream in = sk.getInputStream(); + Map head = readhead(in); + if(!checkhead(head)) + return; + try { + handle(head, sk); + } catch(Exception e) { + logger.log(Level.WARNING, "Could not handle request", e); + return; + } + sk = null; + } finally { + if(sk != null) + sk.close(); + } + } catch(IOException e) { + logger.log(Level.WARNING, "I/O error encountered while serving SCGI request", e); + } + } + + public void run() { + try { + try { + while(true) { + Socket nsk = sk.accept(); + serve(nsk); + } + } finally { + sk.close(); + } + } catch(IOException e) { + logger.log(Level.SEVERE, "SCGI server encountered I/O error", e); + } + } +} diff --git a/src/dolda/jsvc/util/JarContext.java b/src/dolda/jsvc/util/JarContext.java new file mode 100644 index 0000000..cb16d37 --- /dev/null +++ b/src/dolda/jsvc/util/JarContext.java @@ -0,0 +1,93 @@ +package dolda.jsvc.util; + +import java.io.*; +import java.util.*; +import java.net.*; +import dolda.jsvc.*; + +public class JarContext implements ServerContext { + private final long ctime; + private final String name; + public final ClassLoader loader; + protected final Properties sysconfig, libconfig; + + private static String mangle(File f) { + String ret = f.getName(); + int p = ret.lastIndexOf('.'); + if(p > 0) + ret = ret.substring(0, p); + for(f = f.getParentFile(); f != null; f = f.getParentFile()) + ret = f.getName() + "/" + ret; + return(ret); + } + + private void loadconfig() { + try { + InputStream pi = loader.getResourceAsStream("jsvc.properties"); + if(pi != null) { + try { + libconfig.load(pi); + } finally { + pi.close(); + } + } + } catch(IOException e) { + throw(new Error(e)); + } + } + + public Class findboot() { + String clnm = libconfig("jsvc.bootstrap", null); + if(clnm == null) + return(null); + Class bc; + try { + bc = loader.loadClass(clnm); + } catch(ClassNotFoundException e) { + return(null); + } + return(bc); + } + + public JarContext(ClassLoader cl, String name) { + this.ctime = System.currentTimeMillis(); + this.name = name; + this.loader = cl; + sysconfig = new Properties(); + libconfig = new Properties(); + + loadconfig(); + } + + private static URL makingmewanttokilljavac(File jar) { + try { + return(jar.toURI().toURL()); + } catch(MalformedURLException e) { + throw(new RuntimeException(e)); + } + } + + public JarContext(File jar) { + this(new URLClassLoader(new URL[] {makingmewanttokilljavac(jar)}, JarContext.class.getClassLoader()), mangle(jar)); + } + + public long starttime() { + return(ctime); + } + + public String name() { + return(name); + } + + public String sysconfig(String key, String def) { + return(sysconfig.getProperty(key, def)); + } + + public String libconfig(String key, String def) { + return(libconfig.getProperty(key, def)); + } + + public RequestThread worker(Responder root, Request req, ThreadGroup tg, String name) { + return(new RequestThread(root, req, tg, name)); + } +} diff --git a/src/dolda/jsvc/util/Misc.java b/src/dolda/jsvc/util/Misc.java index 34e36f7..9b7f8e1 100644 --- a/src/dolda/jsvc/util/Misc.java +++ b/src/dolda/jsvc/util/Misc.java @@ -6,6 +6,7 @@ import java.io.*; public class Misc { public static final java.nio.charset.Charset utf8 = java.nio.charset.Charset.forName("UTF-8"); + public static final java.nio.charset.Charset ascii = java.nio.charset.Charset.forName("US-ASCII"); private static Map stext = new HashMap(); static { diff --git a/src/dolda/jsvc/util/ResponseBuffer.java b/src/dolda/jsvc/util/ResponseBuffer.java index c949464..96d4563 100644 --- a/src/dolda/jsvc/util/ResponseBuffer.java +++ b/src/dolda/jsvc/util/ResponseBuffer.java @@ -31,7 +31,7 @@ public abstract class ResponseBuffer implements ResettableRequest { throw(new IllegalStateException("Response has been flushed; header information cannot be modified")); } - private void flush() { + private void flush() throws IOException { if(flushed) return; if(respcode < 0) { @@ -100,6 +100,6 @@ public abstract class ResponseBuffer implements ResettableRequest { init(); } - protected abstract void backflush(); + protected abstract void backflush() throws IOException; protected abstract OutputStream realoutput(); } -- 2.11.0 From a13bfa2c65aa523f9981531f3db1f125fbcfc19e Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 8 Sep 2010 05:14:24 +0200 Subject: [PATCH 04/16] Extended SCGI DirServer with some logging. --- src/dolda/jsvc/scgi/DirServer.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dolda/jsvc/scgi/DirServer.java b/src/dolda/jsvc/scgi/DirServer.java index 7bf064d..bd8d43c 100644 --- a/src/dolda/jsvc/scgi/DirServer.java +++ b/src/dolda/jsvc/scgi/DirServer.java @@ -3,6 +3,7 @@ package dolda.jsvc.scgi; import java.io.*; import java.net.*; import java.util.*; +import java.util.logging.*; import dolda.jsvc.*; import dolda.jsvc.util.*; import dolda.jsvc.j2ee.PosixArgs; @@ -10,6 +11,7 @@ import dolda.jsvc.j2ee.PosixArgs; public class DirServer extends Server { private final Map contexts = new HashMap(); private final File datroot; + private final Logger logger = Logger.getLogger("dolda.jsvc.scgi.dirserver"); public DirServer(ServerSocket sk, File datroot) { super(sk); @@ -19,16 +21,19 @@ public class DirServer extends Server { private DSContext context(File file) throws ThreadContext.CreateException { synchronized(contexts) { DSContext ctx = contexts.get(file); + String act = "loaded %s as %s"; if(ctx != null) { if(ctx.mtime < file.lastModified()) { ctx.tg.destroy(); contexts.remove(file); ctx = null; + act = "reloaded %s as %s"; } } if(ctx == null) { ctx = new DSContext(file, datroot); contexts.put(file, ctx); + logger.config(String.format(act, file, ctx.name())); } return(ctx); } -- 2.11.0 From 22779185ec3cd3ab6fafdbcbe675161ceae9ce7d Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Fri, 10 Sep 2010 06:29:04 +0200 Subject: [PATCH 05/16] Improved SCGI server's handling of its environment. --- src/dolda/jsvc/scgi/DSContext.java | 47 +++++++----- src/dolda/jsvc/scgi/DirServer.java | 17 ++--- src/dolda/jsvc/scgi/Environment.java | 87 ++++++++++++++++++++++ .../util/{JarContext.java => SimpleContext.java} | 40 +--------- 4 files changed, 123 insertions(+), 68 deletions(-) create mode 100644 src/dolda/jsvc/scgi/Environment.java rename src/dolda/jsvc/util/{JarContext.java => SimpleContext.java} (53%) diff --git a/src/dolda/jsvc/scgi/DSContext.java b/src/dolda/jsvc/scgi/DSContext.java index 9103525..c726887 100644 --- a/src/dolda/jsvc/scgi/DSContext.java +++ b/src/dolda/jsvc/scgi/DSContext.java @@ -1,40 +1,47 @@ package dolda.jsvc.scgi; import java.io.*; +import java.net.*; import dolda.jsvc.*; import dolda.jsvc.util.*; -public class DSContext extends JarContext { +public class DSContext extends SimpleContext { public final long mtime; - private final File datroot; public final ThreadContext tg; + private final Environment env; - public DSContext(File jar, File datroot) throws ThreadContext.CreateException { - super(jar); + private static String mangle(File f) { + String ret = f.getName(); + int p = ret.lastIndexOf('.'); + if(p > 0) + ret = ret.substring(0, p); + for(f = f.getParentFile(); f != null; f = f.getParentFile()) + ret = f.getName() + "/" + ret; + return(ret); + } + + private static URL makingmewanttokilljavac(File jar) { + try { + return(jar.toURI().toURL()); + } catch(MalformedURLException e) { + throw(new RuntimeException(e)); + } + } + + public DSContext(File jar, Environment env) throws ThreadContext.CreateException { + super(URLClassLoader.newInstance(new URL[] {makingmewanttokilljavac(jar)}, env.libloader()), mangle(jar)); this.mtime = jar.lastModified(); - this.datroot = datroot; + this.env = env; loadconfig(); this.tg = ThreadContext.create(this, loader); } private void loadconfig() { - if(datroot != null) { - File sroot = new File(new File(datroot, "store"), name()); + if(env.root != null) { + File sroot = new File(new File(env.root, "store"), name()); sysconfig.put("jsvc.storage", "file:" + sroot.getPath()); - File conf = new File(datroot, "jsvc.properties"); - if(conf.exists()) { - try { - InputStream in = new FileInputStream(conf); - try { - sysconfig.load(in); - } finally { - in.close(); - } - } catch(IOException e) { - throw(new RuntimeException(e)); - } - } } + sysconfig.putAll(env.sysconfig); } public RequestThread worker(Responder root, Request req, ThreadGroup tg, String name) { diff --git a/src/dolda/jsvc/scgi/DirServer.java b/src/dolda/jsvc/scgi/DirServer.java index bd8d43c..7920977 100644 --- a/src/dolda/jsvc/scgi/DirServer.java +++ b/src/dolda/jsvc/scgi/DirServer.java @@ -10,12 +10,12 @@ import dolda.jsvc.j2ee.PosixArgs; public class DirServer extends Server { private final Map contexts = new HashMap(); - private final File datroot; + private final Environment env; private final Logger logger = Logger.getLogger("dolda.jsvc.scgi.dirserver"); - public DirServer(ServerSocket sk, File datroot) { + public DirServer(ServerSocket sk, Environment env) { super(sk); - this.datroot = datroot; + this.env = env; } private DSContext context(File file) throws ThreadContext.CreateException { @@ -31,7 +31,7 @@ public class DirServer extends Server { } } if(ctx == null) { - ctx = new DSContext(file, datroot); + ctx = new DSContext(file, env); contexts.put(file, ctx); logger.config(String.format(act, file, ctx.name())); } @@ -85,11 +85,8 @@ public class DirServer extends Server { usage(System.err); System.exit(1); } - if(datroot == null) { - datroot = new File(System.getProperty("user.home"), ".jsvc"); - if(!datroot.exists() || !datroot.isDirectory()) - datroot = null; - } + Environment env = (datroot == null)?new Environment():new Environment(datroot); + env.initvm(); int port = Integer.parseInt(opt.rest[0]); ServerSocket sk; try { @@ -99,7 +96,7 @@ public class DirServer extends Server { System.exit(1); return; /* Because javac is stupid. :-/ */ } - DirServer s = new DirServer(sk, datroot); + DirServer s = new DirServer(sk, env); if(charset != null) s.headcs = charset; diff --git a/src/dolda/jsvc/scgi/Environment.java b/src/dolda/jsvc/scgi/Environment.java new file mode 100644 index 0000000..e91f57a --- /dev/null +++ b/src/dolda/jsvc/scgi/Environment.java @@ -0,0 +1,87 @@ +package dolda.jsvc.scgi; + +import java.io.*; +import java.net.*; +import java.util.*; + +public class Environment { + public final File root; + public final Properties sysconfig = new Properties(); + private ClassLoader lib = null; + + public Environment(File root) { + this.root = root; + if(root != null) + loadconfig(); + } + + private static File defroot() { + File root = new File(System.getProperty("user.home"), ".jsvc"); + if(root.exists() && root.isDirectory()) + return(root); + return(null); + } + + public Environment() { + this(defroot()); + } + + private void loadconfig() { + File conf = new File(root, "jsvc.properties"); + if(conf.exists()) { + try { + InputStream in = new FileInputStream(conf); + try { + sysconfig.load(in); + } finally { + in.close(); + } + } catch(IOException e) { + throw(new RuntimeException(e)); + } + } + File lib = new File(root, "lib"); + if(lib.exists() && lib.isDirectory()) { + List jars = new ArrayList(); + for(File f : lib.listFiles()) { + if(f.isDirectory()) + continue; + if(!f.canRead()) + continue; + String nm = f.getName(); + if((nm.length() < 4) || !nm.substring(nm.length() - 4).equals(".jar")) + continue; + try { + jars.add(f.toURI().toURL()); + } catch(MalformedURLException e) { + throw(new Error(e)); + } + } + this.lib = URLClassLoader.newInstance(jars.toArray(new URL[0]), Environment.class.getClassLoader()); + } + } + + public ClassLoader libloader() { + if(this.lib == null) + return(Environment.class.getClassLoader()); + return(this.lib); + } + + public void initvm() { + if(root == null) + return; + File logging = new File(root, "logging.properties"); + if(logging.exists() && logging.canRead()) { + try { + InputStream in = new FileInputStream(logging); + try { + java.util.logging.LogManager.getLogManager().readConfiguration(in); + } finally { + in.close(); + } + } catch(IOException e) { + throw(new RuntimeException(e)); + } + } + } +} diff --git a/src/dolda/jsvc/util/JarContext.java b/src/dolda/jsvc/util/SimpleContext.java similarity index 53% rename from src/dolda/jsvc/util/JarContext.java rename to src/dolda/jsvc/util/SimpleContext.java index cb16d37..0849101 100644 --- a/src/dolda/jsvc/util/JarContext.java +++ b/src/dolda/jsvc/util/SimpleContext.java @@ -2,25 +2,14 @@ package dolda.jsvc.util; import java.io.*; import java.util.*; -import java.net.*; import dolda.jsvc.*; -public class JarContext implements ServerContext { +public class SimpleContext implements ServerContext { private final long ctime; private final String name; public final ClassLoader loader; protected final Properties sysconfig, libconfig; - private static String mangle(File f) { - String ret = f.getName(); - int p = ret.lastIndexOf('.'); - if(p > 0) - ret = ret.substring(0, p); - for(f = f.getParentFile(); f != null; f = f.getParentFile()) - ret = f.getName() + "/" + ret; - return(ret); - } - private void loadconfig() { try { InputStream pi = loader.getResourceAsStream("jsvc.properties"); @@ -36,20 +25,7 @@ public class JarContext implements ServerContext { } } - public Class findboot() { - String clnm = libconfig("jsvc.bootstrap", null); - if(clnm == null) - return(null); - Class bc; - try { - bc = loader.loadClass(clnm); - } catch(ClassNotFoundException e) { - return(null); - } - return(bc); - } - - public JarContext(ClassLoader cl, String name) { + public SimpleContext(ClassLoader cl, String name) { this.ctime = System.currentTimeMillis(); this.name = name; this.loader = cl; @@ -59,18 +35,6 @@ public class JarContext implements ServerContext { loadconfig(); } - private static URL makingmewanttokilljavac(File jar) { - try { - return(jar.toURI().toURL()); - } catch(MalformedURLException e) { - throw(new RuntimeException(e)); - } - } - - public JarContext(File jar) { - this(new URLClassLoader(new URL[] {makingmewanttokilljavac(jar)}, JarContext.class.getClassLoader()), mangle(jar)); - } - public long starttime() { return(ctime); } -- 2.11.0 From 99c044eedbd7793092623189563f5bdddc277145 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sat, 11 Sep 2010 06:02:28 +0200 Subject: [PATCH 06/16] Canonicalize HTTP method in ScgiRequest. --- src/dolda/jsvc/scgi/ScgiRequest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/dolda/jsvc/scgi/ScgiRequest.java b/src/dolda/jsvc/scgi/ScgiRequest.java index 192d380..a7931ed 100644 --- a/src/dolda/jsvc/scgi/ScgiRequest.java +++ b/src/dolda/jsvc/scgi/ScgiRequest.java @@ -55,9 +55,12 @@ public class ScgiRequest extends ResponseBuffer { path = environ.get("PATH_INFO"); if(path == null) throw(new InvalidRequestException("Missing PATH_INFO")); - method = environ.get("REQUEST_METHOD"); - if(method == null) - throw(new InvalidRequestException("Missing REQUEST_METHOD")); + { + String tmp = environ.get("REQUEST_METHOD"); + if(tmp == null) + throw(new InvalidRequestException("Missing REQUEST_METHOD")); + method = tmp.toUpperCase().intern(); + } { /* Ewwww, this is disgusting! */ String scheme = "http"; -- 2.11.0 From ecbf37771d3f59836e5930caf5f1b2665478eb92 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sat, 11 Sep 2010 06:02:49 +0200 Subject: [PATCH 07/16] Fixed DirServer shutdown bug. --- src/dolda/jsvc/scgi/DirServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dolda/jsvc/scgi/DirServer.java b/src/dolda/jsvc/scgi/DirServer.java index 7920977..8f19d8a 100644 --- a/src/dolda/jsvc/scgi/DirServer.java +++ b/src/dolda/jsvc/scgi/DirServer.java @@ -24,7 +24,7 @@ public class DirServer extends Server { String act = "loaded %s as %s"; if(ctx != null) { if(ctx.mtime < file.lastModified()) { - ctx.tg.destroy(); + ctx.tg.shutdown(); contexts.remove(file); ctx = null; act = "reloaded %s as %s"; -- 2.11.0 From b9089d950e376b3f48c63e69c72a32d4efeba7b0 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sat, 11 Sep 2010 06:03:50 +0200 Subject: [PATCH 08/16] Honor jsvc.forcelimit. --- src/dolda/jsvc/ThreadContext.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dolda/jsvc/ThreadContext.java b/src/dolda/jsvc/ThreadContext.java index d0e32fd..b6d9677 100644 --- a/src/dolda/jsvc/ThreadContext.java +++ b/src/dolda/jsvc/ThreadContext.java @@ -72,7 +72,10 @@ public class ThreadContext extends ThreadGroup { if(st.st == "killed") logger.log(Level.WARNING, "Thread " + rt + " refused to die; killing again"); if(now - st.lastkill > 5000) { - rt.stop(); + if(forcelimit) + rt.stop(); + else + rt.interrupt(); st.st = "killed"; st.lastkill = now; } else { -- 2.11.0 From ad522c7697632eea5751fd6adffbaa73ace97407 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sat, 11 Sep 2010 06:31:39 +0200 Subject: [PATCH 09/16] Added a sysconfig variable for global error debugging in ErrorHandler. --- src/dolda/jsvc/util/ErrorHandler.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dolda/jsvc/util/ErrorHandler.java b/src/dolda/jsvc/util/ErrorHandler.java index d840ee7..ca4900a 100644 --- a/src/dolda/jsvc/util/ErrorHandler.java +++ b/src/dolda/jsvc/util/ErrorHandler.java @@ -52,6 +52,11 @@ public class ErrorHandler implements Responder { } protected boolean debug(Request req, Throwable t) { + ThreadContext thc = ThreadContext.current(); + if(thc != null) { + if(Misc.boolval(thc.server().sysconfig("jsvc.debug-errors", "0"))) + return(true); + } SocketAddress rem = req.remoteaddr(); return((rem instanceof InetSocketAddress) && ((InetSocketAddress)rem).getAddress().isLoopbackAddress()); } -- 2.11.0 From 0de513742ae180a370c7836aa285cc82a8e95f22 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sat, 11 Sep 2010 19:26:28 +0200 Subject: [PATCH 10/16] Added a way to properly shut down the SCGI server. --- src/dolda/jsvc/scgi/DirServer.java | 11 +++++++++++ src/dolda/jsvc/scgi/Server.java | 29 +++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/dolda/jsvc/scgi/DirServer.java b/src/dolda/jsvc/scgi/DirServer.java index 8f19d8a..d238504 100644 --- a/src/dolda/jsvc/scgi/DirServer.java +++ b/src/dolda/jsvc/scgi/DirServer.java @@ -51,6 +51,17 @@ public class DirServer extends Server { RequestThread w = ctx.tg.respond(req); w.start(); } + + protected void shutdown() { + synchronized(contexts) { + for(Iterator> i = contexts.entrySet().iterator(); i.hasNext();) { + Map.Entry e = i.next(); + DSContext ctx = e.getValue(); + i.remove(); + ctx.tg.shutdown(); + } + } + } private static void usage(PrintStream out) { out.println("usage: dolda.jsvc.scgi.DirServer [-h] [-e CHARSET] [-d DATADIR] PORT"); diff --git a/src/dolda/jsvc/scgi/Server.java b/src/dolda/jsvc/scgi/Server.java index f01eae9..f8d4637 100644 --- a/src/dolda/jsvc/scgi/Server.java +++ b/src/dolda/jsvc/scgi/Server.java @@ -8,6 +8,7 @@ import java.util.*; public abstract class Server implements Runnable { private final ServerSocket sk; private final Logger logger = Logger.getLogger("dolda.jsvc.scgi"); + private boolean running = false; public String headcs = "UTF-8"; public Server(ServerSocket sk) { @@ -97,7 +98,12 @@ public abstract class Server implements Runnable { public void run() { try { try { - while(true) { + synchronized(this) { + if(running) + throw(new IllegalStateException("SCGI server is already running")); + running = true; + } + while(running) { Socket nsk = sk.accept(); serve(nsk); } @@ -105,7 +111,26 @@ public abstract class Server implements Runnable { sk.close(); } } catch(IOException e) { - logger.log(Level.SEVERE, "SCGI server encountered I/O error", e); + if((e instanceof SocketException) && !running) { + /* Assume that stop() has closed the socket. */ + } else { + logger.log(Level.SEVERE, "SCGI server encountered I/O error", e); + } + } finally { + shutdown(); + running = false; } } + + public void stop() { + try { + running = false; + sk.close(); + } catch(IOException e) { + throw(new RuntimeException(e)); + } + } + + protected void shutdown() { + } } -- 2.11.0 From 48d0e295cb79948858eb5aa99cc08cdf9f9e4546 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sat, 11 Sep 2010 21:23:39 +0200 Subject: [PATCH 11/16] Add a VM shutdown hook in DirServer to shut down contexts properly. --- src/dolda/jsvc/scgi/DirServer.java | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/dolda/jsvc/scgi/DirServer.java b/src/dolda/jsvc/scgi/DirServer.java index d238504..1ac452c 100644 --- a/src/dolda/jsvc/scgi/DirServer.java +++ b/src/dolda/jsvc/scgi/DirServer.java @@ -12,6 +12,7 @@ public class DirServer extends Server { private final Map contexts = new HashMap(); private final Environment env; private final Logger logger = Logger.getLogger("dolda.jsvc.scgi.dirserver"); + private Thread sdhook = null, main = null; public DirServer(ServerSocket sk, Environment env) { super(sk); @@ -52,7 +53,21 @@ public class DirServer extends Server { w.start(); } + private class ShutdownHandler extends Thread { + public void run() { + sdhook = null; + DirServer.this.stop(); + try { + main.join(); + } catch(InterruptedException e) {} + } + } + protected void shutdown() { + try { + if(sdhook != null) + Runtime.getRuntime().removeShutdownHook(sdhook); + } catch(Exception e) {} synchronized(contexts) { for(Iterator> i = contexts.entrySet().iterator(); i.hasNext();) { Map.Entry e = i.next(); @@ -61,6 +76,7 @@ public class DirServer extends Server { ctx.tg.shutdown(); } } + super.shutdown(); } private static void usage(PrintStream out) { @@ -111,6 +127,8 @@ public class DirServer extends Server { if(charset != null) s.headcs = charset; - new Thread(s, "SCGI server thread").start(); + Runtime.getRuntime().addShutdownHook(s.sdhook = s.new ShutdownHandler()); + s.main = new Thread(s, "SCGI server thread"); + s.main.start(); } } -- 2.11.0 From ca735b1ffb58436bbd0ab9855625560c28f21424 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sat, 11 Sep 2010 21:26:11 +0200 Subject: [PATCH 12/16] Moved the SCGI storage root default into Environment. --- src/dolda/jsvc/scgi/DSContext.java | 4 ---- src/dolda/jsvc/scgi/Environment.java | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dolda/jsvc/scgi/DSContext.java b/src/dolda/jsvc/scgi/DSContext.java index c726887..350e1c5 100644 --- a/src/dolda/jsvc/scgi/DSContext.java +++ b/src/dolda/jsvc/scgi/DSContext.java @@ -37,10 +37,6 @@ public class DSContext extends SimpleContext { } private void loadconfig() { - if(env.root != null) { - File sroot = new File(new File(env.root, "store"), name()); - sysconfig.put("jsvc.storage", "file:" + sroot.getPath()); - } sysconfig.putAll(env.sysconfig); } diff --git a/src/dolda/jsvc/scgi/Environment.java b/src/dolda/jsvc/scgi/Environment.java index e91f57a..b2f85bf 100644 --- a/src/dolda/jsvc/scgi/Environment.java +++ b/src/dolda/jsvc/scgi/Environment.java @@ -27,6 +27,8 @@ public class Environment { } private void loadconfig() { + File sroot = new File(root, "store"); + sysconfig.put("jsvc.storage", "file:" + sroot.getPath()); File conf = new File(root, "jsvc.properties"); if(conf.exists()) { try { -- 2.11.0 From 7f4f5d6a9cb16a9dbcd2bae49bdbd1bc1f13dc34 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sun, 12 Sep 2010 02:28:16 +0200 Subject: [PATCH 13/16] Revert "Moved the SCGI storage root default into Environment." This reverts commit ca735b1ffb58436bbd0ab9855625560c28f21424, since the code didn't respect context names with that. --- src/dolda/jsvc/scgi/DSContext.java | 4 ++++ src/dolda/jsvc/scgi/Environment.java | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dolda/jsvc/scgi/DSContext.java b/src/dolda/jsvc/scgi/DSContext.java index 350e1c5..c726887 100644 --- a/src/dolda/jsvc/scgi/DSContext.java +++ b/src/dolda/jsvc/scgi/DSContext.java @@ -37,6 +37,10 @@ public class DSContext extends SimpleContext { } private void loadconfig() { + if(env.root != null) { + File sroot = new File(new File(env.root, "store"), name()); + sysconfig.put("jsvc.storage", "file:" + sroot.getPath()); + } sysconfig.putAll(env.sysconfig); } diff --git a/src/dolda/jsvc/scgi/Environment.java b/src/dolda/jsvc/scgi/Environment.java index b2f85bf..e91f57a 100644 --- a/src/dolda/jsvc/scgi/Environment.java +++ b/src/dolda/jsvc/scgi/Environment.java @@ -27,8 +27,6 @@ public class Environment { } private void loadconfig() { - File sroot = new File(root, "store"); - sysconfig.put("jsvc.storage", "file:" + sroot.getPath()); File conf = new File(root, "jsvc.properties"); if(conf.exists()) { try { -- 2.11.0 From 152e6a84d89f748c1dc4877594702732a6147c4f Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sun, 12 Sep 2010 06:16:23 +0200 Subject: [PATCH 14/16] DocBuffer.asnode: Convert null properly. --- src/dolda/jsvc/next/DocBuffer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dolda/jsvc/next/DocBuffer.java b/src/dolda/jsvc/next/DocBuffer.java index d885a0b..b936c0a 100644 --- a/src/dolda/jsvc/next/DocBuffer.java +++ b/src/dolda/jsvc/next/DocBuffer.java @@ -77,6 +77,8 @@ public class DocBuffer { } public Node asnode(Object o) { + if(o == null) + return(text("")); if(o instanceof Node) { Node n = (Node)o; if(n.getOwnerDocument() != doc) -- 2.11.0 From 3299417335913b0886ca80ac0de62d76fc741dbe Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Mon, 28 Mar 2011 21:29:32 +0200 Subject: [PATCH 15/16] Added some JMX support. --- src/dolda/jsvc/ThreadContext.java | 4 ++++ src/dolda/jsvc/jmx/Context.java | 30 ++++++++++++++++++++++++++++++ src/dolda/jsvc/jmx/ContextMBean.java | 6 ++++++ src/dolda/jsvc/scgi/DSContext.java | 2 ++ src/dolda/jsvc/scgi/DirServer.java | 26 +++++++++++++++++++++++++- src/dolda/jsvc/scgi/jmx/Server.java | 25 +++++++++++++++++++++++++ src/dolda/jsvc/scgi/jmx/ServerMBean.java | 5 +++++ 7 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/dolda/jsvc/jmx/Context.java create mode 100644 src/dolda/jsvc/jmx/ContextMBean.java create mode 100644 src/dolda/jsvc/scgi/jmx/Server.java create mode 100644 src/dolda/jsvc/scgi/jmx/ServerMBean.java diff --git a/src/dolda/jsvc/ThreadContext.java b/src/dolda/jsvc/ThreadContext.java index b6d9677..8e544d3 100644 --- a/src/dolda/jsvc/ThreadContext.java +++ b/src/dolda/jsvc/ThreadContext.java @@ -143,6 +143,10 @@ public class ThreadContext extends ThreadGroup { return(ctx.worker(root, req, workers, "Worker thread " + reqs++)); } + public long requests() { + return(reqs); + } + private Responder bootstrap(final Class bootclass) { final Throwable[] err = new Throwable[1]; final Responder[] res = new Responder[1]; diff --git a/src/dolda/jsvc/jmx/Context.java b/src/dolda/jsvc/jmx/Context.java new file mode 100644 index 0000000..2f062f6 --- /dev/null +++ b/src/dolda/jsvc/jmx/Context.java @@ -0,0 +1,30 @@ +package dolda.jsvc.jmx; + +import javax.management.*; +import java.util.*; +import dolda.jsvc.*; + +public class Context implements ContextMBean { + public final ObjectName name; + private final ThreadContext b; + + public Context(ThreadContext ctx) { + this.b = ctx; + Hashtable info = new Hashtable(); + info.put("type", "Context"); + info.put("name", getname()); + try { + name = new ObjectName("dolda.jsvc", info); + } catch(MalformedObjectNameException e) { + throw(new RuntimeException(e)); + } + } + + public long getrequests() { + return(b.requests()); + } + + public String getname() { + return(b.server().name()); + } +} diff --git a/src/dolda/jsvc/jmx/ContextMBean.java b/src/dolda/jsvc/jmx/ContextMBean.java new file mode 100644 index 0000000..4dcdee9 --- /dev/null +++ b/src/dolda/jsvc/jmx/ContextMBean.java @@ -0,0 +1,6 @@ +package dolda.jsvc.jmx; + +public interface ContextMBean { + public String getname(); + public long getrequests(); +} diff --git a/src/dolda/jsvc/scgi/DSContext.java b/src/dolda/jsvc/scgi/DSContext.java index c726887..09fe1a9 100644 --- a/src/dolda/jsvc/scgi/DSContext.java +++ b/src/dolda/jsvc/scgi/DSContext.java @@ -9,6 +9,7 @@ public class DSContext extends SimpleContext { public final long mtime; public final ThreadContext tg; private final Environment env; + public final dolda.jsvc.jmx.Context mbean; private static String mangle(File f) { String ret = f.getName(); @@ -34,6 +35,7 @@ public class DSContext extends SimpleContext { this.env = env; loadconfig(); this.tg = ThreadContext.create(this, loader); + mbean = new dolda.jsvc.jmx.Context(tg); } private void loadconfig() { diff --git a/src/dolda/jsvc/scgi/DirServer.java b/src/dolda/jsvc/scgi/DirServer.java index 1ac452c..5d82739 100644 --- a/src/dolda/jsvc/scgi/DirServer.java +++ b/src/dolda/jsvc/scgi/DirServer.java @@ -7,10 +7,12 @@ import java.util.logging.*; import dolda.jsvc.*; import dolda.jsvc.util.*; import dolda.jsvc.j2ee.PosixArgs; +import java.lang.management.ManagementFactory; +import javax.management.*; public class DirServer extends Server { private final Map contexts = new HashMap(); - private final Environment env; + public final Environment env; private final Logger logger = Logger.getLogger("dolda.jsvc.scgi.dirserver"); private Thread sdhook = null, main = null; @@ -26,6 +28,11 @@ public class DirServer extends Server { if(ctx != null) { if(ctx.mtime < file.lastModified()) { ctx.tg.shutdown(); + try { + ManagementFactory.getPlatformMBeanServer().unregisterMBean(ctx.mbean.name); + } catch(InstanceNotFoundException e) { + } catch(MBeanRegistrationException e) { + } contexts.remove(file); ctx = null; act = "reloaded %s as %s"; @@ -33,6 +40,12 @@ public class DirServer extends Server { } if(ctx == null) { ctx = new DSContext(file, env); + try { + ManagementFactory.getPlatformMBeanServer().registerMBean(ctx.mbean, ctx.mbean.name); + } catch(InstanceAlreadyExistsException e) { + } catch(MBeanRegistrationException e) { + } catch(NotCompliantMBeanException e) { + } contexts.put(file, ctx); logger.config(String.format(act, file, ctx.name())); } @@ -77,6 +90,11 @@ public class DirServer extends Server { } } super.shutdown(); + try { + ManagementFactory.getPlatformMBeanServer().unregisterMBean(dolda.jsvc.scgi.jmx.Server.name); + } catch(InstanceNotFoundException e) { + } catch(MBeanRegistrationException e) { + } } private static void usage(PrintStream out) { @@ -124,6 +142,12 @@ public class DirServer extends Server { return; /* Because javac is stupid. :-/ */ } DirServer s = new DirServer(sk, env); + try { + ManagementFactory.getPlatformMBeanServer().registerMBean(new dolda.jsvc.scgi.jmx.Server(s), dolda.jsvc.scgi.jmx.Server.name); + } catch(InstanceAlreadyExistsException e) { + } catch(MBeanRegistrationException e) { + } catch(NotCompliantMBeanException e) { + } if(charset != null) s.headcs = charset; diff --git a/src/dolda/jsvc/scgi/jmx/Server.java b/src/dolda/jsvc/scgi/jmx/Server.java new file mode 100644 index 0000000..573e9a7 --- /dev/null +++ b/src/dolda/jsvc/scgi/jmx/Server.java @@ -0,0 +1,25 @@ +package dolda.jsvc.scgi.jmx; + +import javax.management.*; +import dolda.jsvc.scgi.*; + +public class Server implements ServerMBean { + public static final ObjectName name; + private final DirServer b; + + static { + try { + name = new ObjectName("dolda.jsvc", "type", "ScgiServer"); + } catch(MalformedObjectNameException e) { + throw(new RuntimeException(e)); + } + } + + public Server(DirServer b) { + this.b = b; + } + + public String getdataroot() { + return(b.env.root.getPath()); + } +} diff --git a/src/dolda/jsvc/scgi/jmx/ServerMBean.java b/src/dolda/jsvc/scgi/jmx/ServerMBean.java new file mode 100644 index 0000000..f3131f3 --- /dev/null +++ b/src/dolda/jsvc/scgi/jmx/ServerMBean.java @@ -0,0 +1,5 @@ +package dolda.jsvc.scgi.jmx; + +public interface ServerMBean { + public String getdataroot(); +} -- 2.11.0 From f12fa57b8c0c5aef786a0111fd78cdb61dde9595 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Tue, 29 Mar 2011 07:04:05 +0200 Subject: [PATCH 16/16] Fixed DirServer argument parsing. --- src/dolda/jsvc/scgi/DirServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dolda/jsvc/scgi/DirServer.java b/src/dolda/jsvc/scgi/DirServer.java index 5d82739..209d1a4 100644 --- a/src/dolda/jsvc/scgi/DirServer.java +++ b/src/dolda/jsvc/scgi/DirServer.java @@ -102,7 +102,7 @@ public class DirServer extends Server { } public static void main(String[] args) { - PosixArgs opt = PosixArgs.getopt(args, "h"); + PosixArgs opt = PosixArgs.getopt(args, "he:d:"); if(opt == null) { usage(System.err); System.exit(1); -- 2.11.0