added a FEATURE comment
[org.ibex.jinetd.git] / src / org / ibex / jinetd / Port.java
index 2aded0a..8a92dd7 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+
 package org.ibex.jinetd;
 import org.ibex.util.*;
 import org.ibex.io.*;
@@ -6,18 +10,28 @@ import java.util.*;
 import java.net.*;
 import java.util.zip.*;
 
+
+// Feature: port-level redirects
 public class Port extends Loader {
 
-    private final NetworkInterface iface;
-    private final int port;
-    private final Thread listener;
+    final InetAddress bindTo;
+    final int port;
 
-    public Port(String path, NetworkInterface iface, int port) {
+    private static Hash cache = new Hash();
+    public static Port newPort(String path, InetAddress bindTo, int port) throws IOException {
+        String canonical = new File(path).getCanonicalPath();
+        Port p = (Port)cache.get(canonical);
+        if (p == null) cache.put(canonical, p = new Port(path, bindTo, port));
+        else Log.warn(Port.class, "   sharing " + bindTo+":"+port+" -> "+ (p.bindTo+":"+p.port));
+        p.spawn(bindTo, port);
+        return p;
+    }
+
+    void spawn(InetAddress bindTo, int port) { new PortThread(bindTo, port).start(); }
+    private Port(String path, InetAddress bindTo, int port) {
         super(path);
-        this.iface = iface;
+        this.bindTo = bindTo;
         this.port = port;
-        this.listener = new PortThread();
-        listener.start();
     }
 
     public void changed(Watched w) {
@@ -25,73 +39,94 @@ public class Port extends Loader {
         super.changed(w);
     }
 
-    void dispatch(final Connection conn) throws Exception {
+
+    Class isListener(String name) throws ClassNotFoundException {
+        final ClassLoader cl = getClassLoader();
+        final Class c = cl.loadClass(name);
+        if (c == null) return null;
+        if (Listener.class.isAssignableFrom(c) && c != Listener.class) return c;
+        return null;
+    }
+
+    Class findListener() throws Exception {
         getClassLoader();
         String[] list = list();
         for(int i=0; i<list.length; i++) {
             if (!list[i].endsWith(".jar")) continue;
             //Log.warn(this, "checking " + (this.path + File.separatorChar + list[i]));
             File f = new File(this.path + File.separatorChar + list[i]);
-            ZipInputStream zis = new ZipInputStream(new FileInputStream(f));
-            for(ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
-                String name = ze.getName();
-                if (name.endsWith(".class"))
-                    dispatch(conn, name.substring(0, name.length() - ".class".length()).replace('/', '.'));
-            }
+            FileInputStream fis = null;
+            try {
+                fis = new FileInputStream(f);
+                ZipInputStream zis = new ZipInputStream(fis);
+                for(ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
+                    String name = ze.getName();
+                    if (name.endsWith(".class")) {
+                        String classname = name.substring(0, name.length() - ".class".length()).replace('/', '.');
+                        Class c = isListener(classname);
+                        if (c != null) return c;
+                    }
+                }
+            } finally { if (fis != null) fis.close(); }
         }
-        check(conn, new File(getAbsolutePath() + File.separatorChar + "BIN"));
+        return findListener(new File(getAbsolutePath() + File.separatorChar + "BIN"));
     }
 
-    void check(Connection conn, File f) throws Exception {
-        //Log.warn(this, "check(" + f.getAbsolutePath() + ")");
-        if (!f.exists()) return;
+    Class findListener(File f) throws Exception {
+        if (!f.exists()) return null;
         if (!f.isDirectory()) {
-            if (!f.getAbsolutePath().endsWith(".class")) return;
+            if (!f.getAbsolutePath().endsWith(".class")) return null;
             String name = f.getAbsolutePath().substring(getAbsolutePath().length() + 5);
             name = name.substring(0, name.length() - ".class".length()).replace(File.separatorChar, '.');
-            dispatch(conn, name);
+            Class c = isListener(name);
+            if (c != null) return c;
         } else {
             String[] list = f.list();
-            for(int i=0; i<list.length; i++) check(conn, new File(f.getAbsolutePath() + File.separatorChar + list[i]));
+            for(int i=0; i<list.length; i++) {
+                String classname = f.getAbsolutePath() + File.separatorChar + list[i];
+                Class c = findListener(new File(classname));
+                if (c != null) return c;
+            }
         }
+        return null;
     }
-    void dispatch(final Connection conn, String name) throws Exception {
-        //Log.info(this, "attempting class " + name);
-        try {
-            Class c = getClassLoader().loadClass(name);
-            if (c != null) {
-                if (Listener.class.isAssignableFrom(c)) {
-                    Log.error(this, "dispatching connection on port " + port + " to " +
-                              c.getName());
-                    final Listener l = (Listener)c.newInstance();
-                    new Thread() { public void run() {
-                        Log.clearnotes();
-                        try {
-                            l.accept(conn);
-                        } catch (Exception e) {
-                            Log.error(l.getClass(), "Listener threw exception");
-                            Log.error(l.getClass(), e);
-                        } finally {
-                            conn.close();
-                        }
-                    } }.start();
-                    return;
-                }
+
+    void dispatch(final Connection conn) throws Exception {
+        new Thread(tg, new Runnable() { public void run() {
+            String local = conn.getLocalAddress() + ":" + conn.getLocalPort();
+            String remote = conn.getRemoteHostname() + ":" + conn.getRemotePort();
+            try {
+                Class c = findListener();
+                if (c == null) throw new RuntimeException("couldn't find listener");
+                Log.info("["+local+"]", "connection from " + remote + " => " + c.getName());
+                Log.clearnotes();
+                final ClassLoader cl = getClassLoader();
+                Thread.currentThread().setContextClassLoader(cl);
+                Listener l = (Listener)c.newInstance();
+                l.accept(conn);
+            } catch (org.ibex.io.Stream.EOF eof) {
+                Log.warn(this, "end of stream reached handling connection from " +
+                         conn.getRemoteHostname() + ":" + conn.getRemotePort());
+            } catch (Exception e) {
+                Log.error(this, e);
+                conn.close();
+            } finally {
+                conn.close();
             }
-        } catch (Exception e) {
-            Log.error(this, e);
-        }
+        } }).start();
     }
 
     private class PortThread extends Thread {
+        InetAddress bindTo;
+        int port;
+        public PortThread(InetAddress bindTo, int port) { this.bindTo = bindTo; this.port = port; }
         public void run() {
             try {
-                Log.warn(this, "Now listening on interface " + iface + ", port " + port);
-                ServerSocket ss = new ServerSocket(port);
+                Log.warn(this, "Now listening on address " + (bindTo == null ? "all interfaces" : bindTo.toString()) +
+                         ", port " + port);
+                ServerSocket ss = bindTo == null ? new ServerSocket(port) : new ServerSocket(port, 0, bindTo);
                 for(Socket s = ss.accept(); ; s = ss.accept()) try {
-                    Log.warn(this, "accepted connection on port " + port);
                     dispatch(new Connection(s, "megacz.com"));
-                    Log.warn(this, "done searching for service on port " + port);
                 } catch (Exception e) { Log.warn(Port.class, e); }
             } catch (Exception e) { Log.error(Port.class, e);
             } catch (Throwable t) {