`
yunchow
  • 浏览: 319131 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

基于Servlet实现RMI突破防火墙

阅读更多


package com.mypack.web.rmi;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.RMIClassLoader;
import java.rmi.server.UnicastRemoteObject;
import java.util.Hashtable;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * The default RMI socket factory contains several "fallback" mechanisms which
 * enable an RMI client to communicate with a remote server. When an RMI client
 * initiates contact with a remote server, it attempts to establish a connection
 * using each of the following protocols in turn, until one succeeds:
 * 
 * 1. Direct TCP connection. 2. Direct HTTP connection. 3. Proxy connection
 * (SOCKS or HTTP). 4. Connection on port 80 over HTTP to a CGI script. 5. Proxy
 * HTTP connection to CGI script on port 80.
 * 
 * The RMI ServletHandler can be used as replacement for the java-rmi.cgi script
 * that comes with the Java Development Kit (and is invoked in protocols 4 and 5
 * above). The java-rmi.cgi script and the ServletHandler both function as proxy
 * applications that forward remote calls embedded in HTTP to local RMI servers
 * which service these calls. The RMI ServletHandler enables RMI to tunnel
 * remote method calls over HTTP more efficiently than the existing java-rmi.cgi
 * script. The ServletHandler is only loaded once from the servlet
 * administration utility. The script, java-rmi.cgi, is executed once every
 * remote call.
 * 
 * The ServletHandler class contains methods for executing as a Java servlet
 * extension. Because the RMI protocol only makes use of the HTTP post command,
 * the ServletHandler only supports the <code>doPost</code>
 * <code>HttpServlet</code> method. The <code>doPost</code> method of this class
 * interprets a servlet request's query string as a command of the form
 * "<command>=<parameters>". These commands are represented by the abstract
 * interface, <code>RMICommandHandler</code>. Once the <code>doPost</code>
 * method has parsed the requested command, it calls the execute method on one
 * of several command handlers in the <code>commands</code> array.
 * 
 * The command that actually proxies remote calls is the
 * <code>ServletForwardCommand</code>. When the execute method is invoked on the
 * ServletForwardCommand, the command will open a connection on a local port
 * specified by its <code>param</code> parameter and will proceed to write the
 * body of the relevant post request into this connection. It is assumed that an
 * RMI server (e.g. SampleRMIServer) is listening on the local port, "param."
 * The "forward" command will then read the RMI server's response and send this
 * information back to the RMI client as the body of the response to the HTTP
 * post method.
 * 
 * Because the ServletHandler uses a local socket to proxy remote calls, the
 * servlet has the ability to forward remote calls to local RMI objects that
 * reside in the ServletVM or outside of it.
 * 
 * Servlet documentation may be found at the following location:
 * 
 * http://jserv.javasoft.com/products/java-server/documentation/
 * webserver1.0.2/apidoc/Package-javax.servlet.http.html
 */
public class ServletHandler extends HttpServlet implements Runnable {

	/* Variables to hold optional configuration properties. */

	/**
	 * serialVersionUID
	 */
	private static final long serialVersionUID = 1L;

	/** codebase from which this servlet will load remote objects. */
	protected static String initialServerCodebase = null;

	/** name of RMI server class to be created in init method */
	protected static String initialServerClass = null;

	/** name of RMI server class to be created in init method */
	protected static String initialServerBindName = null;

	/**
	 * RMICommandHandler is the abstraction for an object that handles a
	 * particular supported command (for example the "forward" command
	 * "forwards" call information to a remote server on the local machine).
	 * 
	 * The command handler is only used by the ServletHandler so the interface
	 * is protected.
	 */
	protected interface RMICommandHandler {

		/**
		 * Return the string form of the command to be recognized in the query
		 * string.
		 */
		public String getName();

		/**
		 * Execute the command with the given string as parameter.
		 */
		public void execute(HttpServletRequest req, HttpServletResponse res,
				String param) throws ServletClientException,
				ServletServerException, IOException;
	}

	/**
	 * List of handlers for supported commands. A new command will be created
	 * for every service request
	 */
	private static RMICommandHandler commands[] = new RMICommandHandler[] {
			new ServletForwardCommand(), new ServletGethostnameCommand(),
			new ServletPingCommand(), new ServletTryHostnameCommand() };

	/* construct table mapping command strings to handlers */
	private static Hashtable commandLookup;
	static {
		commandLookup = new Hashtable();
		for (int i = 0; i < commands.length; ++i)
			commandLookup.put(commands[i].getName(), commands[i]);
	}

	/**
	 * Once loaded, Java Servlets continue to run until they are unloaded or the
	 * webserver is stopped. This example takes advantage of the extended
	 * Servlet life-cycle and runs a remote object in the Servlet VM.
	 * 
	 * To initialize this remote object the Servlet Administrator should specify
	 * a set of parameters which will be used to download and install an initial
	 * remote server (see readme.txt).
	 * 
	 * If configuration parameters are valid (not blank), the servlet will
	 * attempt to load and start a remote object and a registry in which the
	 * object may be bound.
	 * 
	 * @param config
	 *            Standard configuration object for an http servlet.
	 * 
	 * @exception ServletException
	 *                Calling <code>super.init(config)</code> may cause a
	 *                servlet exception to be thrown.
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);

		try {
			setConfigParameters(config);

			if (!verifyConfigParameters()) {
				// dont export any objects.

				System.err.println("Some optional parameters not set, "
						+ "remote object not exported; "
						+ "ServletHandler is runnning.");

				return;
			}

			/*
			 * RMI requires that a local security manager be responsible for the
			 * method invocations from remote clients - we need to make sure a
			 * security manager is installed.
			 */
			if (System.getSecurityManager() == null) {
				System.setSecurityManager(new RMISecurityManager());
			}

			// create a registry if one is not running already.
			try {
				LocateRegistry.createRegistry(1099);
			} catch (java.rmi.server.ExportException ee) {
				// registry already exists, we'll just use it.
			} catch (RemoteException re) {
				System.err.println(re.getMessage());
				re.printStackTrace();
			}

			/**
			 * Download and create a server object in a thread so we do not
			 * interfere with other servlets. Allow init method to return more
			 * quickly.
			 */
			(new Thread(this)).start();

			System.out.println("RMI Servlet Handler loaded sucessfully.");

		} catch (Exception e) {
			System.err.println("Exception thrown in RMI ServletHandler: "
					+ e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * Create the sample RMI server.
	 */
	public void run() {
		try {
			UnicastRemoteObject server = createRemoteObjectUsingDownloadedClass();
			if (server != null) {
				Naming.rebind(initialServerBindName, server);
				System.err.println("Remote object created successfully.");
			}
		} catch (Exception e) {
			System.err.println("Exception received while intalling object:");
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * Load and export an initial remote object. The implementation class for
	 * this remote object should not be loaded from the servlet's class path;
	 * instead it should be loaded from a URL location that will be accessible
	 * from a remote client. In the case of this example, that location will be
	 * <code>initialServerCodebase</code>
	 */
	UnicastRemoteObject createRemoteObjectUsingDownloadedClass()
			throws Exception {
		UnicastRemoteObject server = null;
		Class serverClass = null;

		int MAX_RETRY = 5;
		int retry = 0;
		int sleep = 2000;

		while ((retry < MAX_RETRY) && (serverClass == null)) {
			try {
				System.err.println("Attempting to load remote class...");
				serverClass = RMIClassLoader.loadClass(new URL(
						initialServerCodebase), initialServerClass);

				// Before we instantiate the obj. make sure it
				// is a UnicastRemoteObject.
				if (!Class.forName("java.rmi.server.UnicastRemoteObject")
						.isAssignableFrom(serverClass)) {
					System.err.println("This example requires an "
							+ " instance of UnicastRemoteObject,"
							+ " remote object not exported.");
				} else {
					System.out.println("Server class loaded successfully...");
					server = ((UnicastRemoteObject) serverClass.newInstance());
				}

			} catch (java.lang.ClassNotFoundException cnfe) {
				retry++;

				/**
				 * The class for the remote object could not be loaded, perhaps
				 * the webserver has not finished initializing itself yet. Try
				 * to load the class a few more times...
				 */
				if (retry >= MAX_RETRY) {
					System.err.println("Failed to load remote server "
							+ " class. Remote object not " + " exported... ");
				} else {
					System.err.println("Could not load remote class, "
							+ "trying again...");
					try {
						Thread.sleep(sleep);
					} catch (InterruptedException ie) {
					}
					continue;
				}
			}
		}
		return server;
	}

	/*
	 * NOTE: If you are using JDK1.2Beta4 or later, it is recommended that you
	 * provide your servlet with a destroy method that will unexport any remote
	 * objects that your servlet ever exports. As mentioned in the readme file
	 * for this example, it is not possible to unexport remote objects in
	 * JDK1.1.x; there is no method in the RMI 1.1 public API that will perform
	 * this task. To restart remote objects in the servlet VM, you will have to
	 * restart your webserver. In JDK1.2x, the methods to unexport a remote
	 * object are as follows:
	 * 
	 * java.rmi.activation.Activatable. unexportObject(Remote obj, boolean
	 * force) java.rmi.server.UnicastRemoteObject. unexportObject(Remote obj,
	 * boolean force)
	 */

	/**
	 * Execute the command given in the servlet request query string. The string
	 * before the first '=' in the queryString is interpreted as the command
	 * name, and the string after the first '=' is the parameters to the
	 * command.
	 * 
	 * @param req
	 *            HTTP servlet request, contains incoming command and arguments
	 * @param res
	 *            HTTP servlet response
	 * @exception ServletException
	 *                and IOException when invoking methods of
	 *                <code>req<code> or <code>res<code>.
	 */
	public void doPost(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {

		try {

			// Command and parameter for this POST request.
			String queryString = req.getQueryString();
			String command, param;
			int delim = queryString.indexOf("=");
			if (delim == -1) {
				command = queryString;
				param = "";
			} else {
				command = queryString.substring(0, delim);
				param = queryString.substring(delim + 1);
			}

			System.out.println("command: " + command);
			System.out.println("param: " + param);

			// lookup command to execute on the client's behalf
			RMICommandHandler handler = (RMICommandHandler) commandLookup
					.get(command);

			// execute the command
			if (handler != null)
				try {
					handler.execute(req, res, param);
				} catch (ServletClientException e) {
					returnClientError(res, "client error: " + e.getMessage());
					e.printStackTrace();
				} catch (ServletServerException e) {
					returnServerError(res, "internal server error: "
							+ e.getMessage());
					e.printStackTrace();
				}
			else
				returnClientError(res, "invalid command: " + command);
		} catch (Exception e) {
			returnServerError(res, "internal error: " + e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * Provide more intelligible errors for methods that are likely to be
	 * called. Let unsupported HTTP "do*" methods result in an error generated
	 * by the super class.
	 * 
	 * @param req
	 *            http Servlet request, contains incoming command and arguments
	 * 
	 * @param res
	 *            http Servlet response
	 * 
	 * @exception ServletException
	 *                and IOException when invoking methods of
	 *                <code>req<code> or <code>res<code>.
	 */
	public void doGet(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {

		returnClientError(res, "GET Operation not supported: "
				+ "Can only forward POST requests.");
	}

	public void doPut(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {

		returnClientError(res, "PUT Operation not supported: "
				+ "Can only forward POST requests.");
	}

	public String getServletInfo() {
		return "RMI Call Forwarding Servlet Servlet.<br>\n";
	}

	/**
	 * Return an HTML error message indicating there was error in the client's
	 * request.
	 * 
	 * @param res
	 *            Servlet response object through which <code>message</code>
	 *            will be written to the client which invoked one of this
	 *            servlet's methods.
	 * @param message
	 *            Error message to be written to client.
	 */
	private static void returnClientError(HttpServletResponse res,
			String message) throws IOException {

		res
				.sendError(HttpServletResponse.SC_BAD_REQUEST, "<HTML><HEAD>"
						+ "<TITLE>Java RMI Client Error</TITLE>" + "</HEAD>"
						+ "<BODY>" + "<H1>Java RMI Client Error</H1>" + message
						+ "</BODY></HTML>");

		System.err.println(HttpServletResponse.SC_BAD_REQUEST
				+ "Java RMI Client Error" + message);
	}

	/**
	 * Return an HTML error message indicating an internal error occurred here
	 * on the server.
	 * 
	 * @param res
	 *            Servlet response object through which <code>message</code>
	 *            will be written to the servlet client.
	 * @param message
	 *            Error message to be written to servlet client.
	 */
	private static void returnServerError(HttpServletResponse res,
			String message) throws IOException {

		res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
				"<HTML><HEAD>" + "<TITLE>Java RMI Server Error</TITLE>"
						+ "</HEAD>" + "<BODY>"
						+ "<H1>Java RMI Server Error</H1>" + message
						+ "</BODY></HTML>");

		System.err.println(HttpServletResponse.SC_INTERNAL_SERVER_ERROR
				+ "Java RMI Server Error: " + message);
	}

	/**
	 * Retrieve parameters from servlet configuration object.
	 * 
	 * @param config
	 *            Standard configuration object for an HTTP servlet.
	 */
	protected synchronized void setConfigParameters(ServletConfig config) {
		try {
			initialServerCodebase = config
					.getInitParameter("rmiservlethandler.initialServerCodebase");
			initialServerClass = config
					.getInitParameter("rmiservlethandler.initialServerClass");
			initialServerBindName = config
					.getInitParameter("rmiservlethandler.initialServerBindName");
		} catch (Exception e) {
			System.err.println("");
			System.err.println("Could not access init parameter:");
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * Ensure that servlet configuration parameters are valid.
	 * 
	 * @return <code>true</code> if all relevant configuration parameters are
	 *         valid (i.e. not "") <code>false</code> otherwise.
	 */
	protected synchronized boolean verifyConfigParameters() {
		return ((verifyParameter("rmiservlethandler.initialServerClass ",
				initialServerClass))
				&& (verifyParameter("rmiservlethandler.initialServerBindName ",
						initialServerBindName)) && (verifyParameter(
				"rmiservlethandler.initialServerCodebase",
				initialServerCodebase)));
	}

	/**
	 * Verify that a single parameter is valid.
	 * 
	 * @return <code>true</code> if the parameter is valid.
	 */
	protected boolean verifyParameter(String parameterName, String parameter) {
		if ((parameter == null) || (parameter.equals(""))) {
			System.err.println("optional parameter is invalid and "
					+ "will not be used: \n    " + parameterName + " = "
					+ parameter);
			return false;
		} else {
			System.err.println(parameterName + " " + "valid: " + parameter);
		}
		return true;
	}

	/*
	 * The ServletHandler class is the only object that needs to access the
	 * CommandHandler subclasses, so we write the commands internal to the
	 * servlet handler.
	 */

	/**
	 * Class that has an execute command to forward request body to local port
	 * on the server and send server reponse back to client.
	 */
	protected static class ServletForwardCommand implements RMICommandHandler {

		public String getName() {
			return "forward";
		}

		/**
		 * Execute the forward command. Forwards data from incoming servlet
		 * request to a port on the local machine. Presumably, an RMI server
		 * will be reading the data that this method sends.
		 * 
		 * @param req
		 *            The servlet request.
		 * @param res
		 *            The servlet response.
		 * @param param
		 *            Port to which data will be sent.
		 */
		public void execute(HttpServletRequest req, HttpServletResponse res,
				String param) throws ServletClientException,
				ServletServerException, IOException {

			int port;
			try {
				port = Integer.parseInt(param);
			} catch (NumberFormatException e) {
				throw new ServletClientException("invalid port number: "
						+ param);
			}
			if (port <= 0 || port > 0xFFFF)
				throw new ServletClientException("invalid port: " + port);
			if (port < 1024)
				throw new ServletClientException("permission denied for port: "
						+ port);

			byte buffer[];
			Socket socket;
			try {
				socket = new Socket(InetAddress.getLocalHost(), port);
			} catch (IOException e) {
				throw new ServletServerException("could not connect to "
						+ "local port");
			}

			// read client's request body
			DataInputStream clientIn = new DataInputStream(req.getInputStream());
			buffer = new byte[req.getContentLength()];
			try {
				clientIn.readFully(buffer);
			} catch (EOFException e) {
				throw new ServletClientException("unexpected EOF "
						+ "reading request body");
			} catch (IOException e) {
				throw new ServletClientException("error reading request"
						+ " body");
			}

			DataOutputStream socketOut = null;
			// send to local server in HTTP
			try {
				socketOut = new DataOutputStream(socket.getOutputStream());
				socketOut.writeBytes("POST / HTTP/1.0\r\n");
				socketOut.writeBytes("Content-length: "
						+ req.getContentLength() + "\r\n\r\n");
				socketOut.write(buffer);
				socketOut.flush();
			} catch (IOException e) {
				throw new ServletServerException("error writing to server");
			}

			// read response from local server
			DataInputStream socketIn;
			try {
				socketIn = new DataInputStream(socket.getInputStream());
			} catch (IOException e) {
				throw new ServletServerException("error reading from "
						+ "server");
			}
			String key = "Content-length:".toLowerCase();
			boolean contentLengthFound = false;
			String line;
			int responseContentLength = -1;
			do {
				try {
					line = socketIn.readLine();
				} catch (IOException e) {
					throw new ServletServerException(
							"error reading from server");
				}
				if (line == null)
					throw new ServletServerException(
							"unexpected EOF reading server response");

				if (line.toLowerCase().startsWith(key)) {
					if (contentLengthFound)
						; // what would we want to do in this case??
					responseContentLength = Integer.parseInt(line.substring(
							key.length()).trim());
					contentLengthFound = true;
				}
			} while ((line.length() != 0) && (line.charAt(0) != '\r')
					&& (line.charAt(0) != '\n'));

			if (!contentLengthFound || responseContentLength < 0)
				throw new ServletServerException(
						"missing or invalid content length in server response");
			buffer = new byte[responseContentLength];
			try {
				socketIn.readFully(buffer);
			} catch (EOFException e) {
				throw new ServletServerException(
						"unexpected EOF reading server response");
			} catch (IOException e) {
				throw new ServletServerException("error reading from server");
			}

			// send local server response back to servlet client
			res.setStatus(HttpServletResponse.SC_OK);
			res.setContentType("application/octet-stream");
			res.setContentLength(buffer.length);

			try {
				OutputStream out = res.getOutputStream();
				out.write(buffer);
				out.flush();
			} catch (IOException e) {
				throw new ServletServerException("error writing response");
			} finally {
				socketOut.close();
				socketIn.close();
			}
		}
	}

	/**
	 * Class that has an execute method to return the host name of the server as
	 * the response body.
	 */
	protected static class ServletGethostnameCommand implements
			RMICommandHandler {

		public String getName() {
			return "gethostname";
		}

		public void execute(HttpServletRequest req, HttpServletResponse res,
				String param) throws ServletClientException,
				ServletServerException, IOException {

			byte[] getHostStringBytes = req.getServerName().getBytes();

			res.setStatus(HttpServletResponse.SC_OK);
			res.setContentType("application/octet-stream");
			res.setContentLength(getHostStringBytes.length);

			OutputStream out = res.getOutputStream();
			out.write(getHostStringBytes);
			out.flush();
		}
	}

	/**
	 * Class that has an execute method to return an OK status to indicate that
	 * connection was successful.
	 */
	protected static class ServletPingCommand implements RMICommandHandler {

		public String getName() {
			return "ping";
		}

		public void execute(HttpServletRequest req, HttpServletResponse res,
				String param) throws ServletClientException,
				ServletServerException, IOException {

			res.setStatus(HttpServletResponse.SC_OK);
			res.setContentType("application/octet-stream");
			res.setContentLength(0);
		}
	}

	/**
	 * Class that has an execute method to return a human readable message
	 * describing which host name is available to local Java VMs.
	 */
	protected static class ServletTryHostnameCommand implements
			RMICommandHandler {

		public String getName() {
			return "hostname";
		}

		public void execute(HttpServletRequest req, HttpServletResponse res,
				String param) throws ServletClientException,
				ServletServerException, IOException {

			PrintWriter pw = res.getWriter();

			pw.println("");
			pw.println("<HTML>" + "<HEAD><TITLE>Java RMI Server Hostname Info"
					+ "</TITLE></HEAD>" + "<BODY>");
			pw.println("<H1>Java RMI Server Hostname Info</H1>");
			pw.println("<H2>Local host name available to Java VM:</H2>");
			pw.print("<P>InetAddress.getLocalHost().getHostName()");
			try {
				String localHostName = InetAddress.getLocalHost().getHostName();

				pw.println(" = " + localHostName);
			} catch (UnknownHostException e) {
				pw.println(" threw java.net.UnknownHostException");
			}

			pw.println("<H2>Server host information obtained through Servlet "
					+ "interface from HTTP server:</H2>");
			pw.println("<P>SERVER_NAME = " + req.getServerName());
			pw.println("<P>SERVER_PORT = " + req.getServerPort());
			pw.println("</BODY></HTML>");
		}
	}

	/**
	 * ServletClientException is thrown when an error is detected in a client's
	 * request.
	 */
	protected static class ServletClientException extends Exception {

		public ServletClientException(String s) {
			super(s);
		}
	}

	/**
	 * ServletServerException is thrown when an error occurs here on the server.
	 */
	protected static class ServletServerException extends Exception {

		public ServletServerException(String s) {
			super(s);
		}
	}
}











0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics