The Servlet Code A Servlet Implementationof HTTP Tunneling

22.4 A Servlet Implementationof HTTP Tunneling

For the fourth and fifth connection strategies to work, there has to be a way, hooked into a web server, to forward method invocations to the actual RMI server. The first implementation of this that Sun Microsystems, Inc. shipped was a compiled CGI script. Formally, it required the following: • Each remote method invocation was sent as a HTTP POST request. • The full URL being used was of the form cgi-binjava-rmi.cgi?forward=[port number]. • The body of the POST contained all the data from the remote request as serialized objects that were then converted into an ASCII stream of characters. After the servlet specification was defined, Sun shipped a servlet that provided the same functionality as the CGI script. In the discussion that follows, we will use a stripped-down version of the SUN class to demonstrate how HTTP tunneling works. In the remainder of this section, we will examine the servlet code in detail.

22.4.1 The Servlet Code

The stripped-down servlet code consists of two main classes and a set of custom exception classes. The main classes are SimplifiedServletHandler and ServletForwardCommand . They have the following roles: SimplifiedServletHandler This class extends HTTPServlet . It receives requests and performs preliminary validation most of which centers around processing the URL. ServletForwardCommand This is a set of static methods that know how to take an HTTP POST and resend it to an RMI server running on the same machine. Heres the code for SimplifiedServletHandler : public class SimplifiedServletHandler extends HttpServlet { public void initServletConfig config throws ServletException { super.initconfig; System.out.printlnSimplified RMI Servlet Handler loaded successfully.; } public void doPostHttpServletRequest req, HttpServletResponse res throws ServletException, IOException { try { String queryString = req.getQueryString ; String command, param; int delim = queryString.indexOf=; if delim == -1 { command = queryString; param = ; } else { command = queryString.substring0, delim; param = queryString.substringdelim + 1; } if command.equalsIgnoreCaseforward { try { ServletForwardCommand.executereq, res, pa ram; } catch ServletClientException e { returnClientErrorres, client error: + e.getMessage ; e.printStackTrace ; } catch ServletServerException e { returnServerErrorres, internal server error: + e. getMessage ; e.printStackTrace ; } } else { returnClientErrorres, invalid command: + command; } } catch Exception e { returnServerErrorres, internal error: + e.getMessage ; e.printStackTrace ; } } public void doGetHttpServletRequest req, HttpServletResponse res throws ServletException, IOException { returnClientErrorres, GET Operation not supported: + Can only forward POST requests.; } public void doPutHttpServletRequest req, HttpServletResponse res throws ServletException, IOException { returnClientErrorres, PUT Operation not supported: + Can only forward POST requests.; } public String getServletInfo { return Simplified RMI Call Forwarding Servlet Servlet.br\n; } private static void returnClientErrorHttpServletResponse res, String message throws IOException { res.sendErrorHttpServletResponse.SC_BAD_REQUEST, HTMLHEADTITLEJava RMI Client ErrorTITLEHEADBODY + H1Java RMI Client ErrorH1 + message + BODYHTML; System.err.printlnHttpServletResponse.SC_BAD_REQUEST + Java RMI Client Error + message; } private static void returnServerErrorHttpServletResponse res, String message throws IOException { res.sendErrorHttpServletResponse.SC_INTERNAL_SERVER_ERROR, HTMLHEAD TITLEJava RMI Server ErrorTITLEHEADBODY + H1Java RMI Server ErrorH1 + message + BODYHTML; System.err.printlnHttpServletResponse.SC_INTERNAL_SERVER_ERROR + Java RMI Server Error: + message; } } Almost all of the processing logic for SimplifiedServletHandler is contained inside the doPost method. The doPost method parses the URL to determine the port on which the server listens. If the URL is a valid URL and contains a port, the request is forwarded to the ServletForwardCommand s static execute method. ServletForwardCommand consists of the following static methods: public static void executeHttpServletRequest request, HttpServletResponse response, String stringifiedPort throws ServletClientException, ServletServerException, IOException { int port = convertStringToPortstringifiedPort; Socket connectionToLocalServer = null; try { connectionToLocalServer = connectToLocalServerport; forwardRequestrequest, connectionToLocalServer; forwardResponseresponse, connectionToLocalServer; } finally { if null=connectionToLocalServer { connectionToLocalServer.close ; } } } private static int convertStringToPortString stringfiedPort throws ServletClientException { int returnValue; try { returnValue = Integer.parseIntstringfiedPort; } catch NumberFormatException e { throw new ServletClientExceptioninvalid port number: + stringfiedPort; } if returnValue = 0 || returnValue 0xFFFF { throw new ServletClientExceptioninvalid port: + returnValue; } if returnValue 1024 { throw new ServletClientExceptionpermission denied for port: + returnValue; } return returnValue; } private static Socket connectToLocalSe rverint port throws ServletServerException { Socket returnValue; try { returnValue = new SocketInetAddress.getLocalHost , port; } catch IOException e { throw new ServletServerExceptioncould not connect to + local port; } return returnValue; } private static void forwardResponseHttpServletResponse response, Socket connectionToLocalServer throws IOException, ServletClientException, ServletServerException { byte[] buffer; DataInputStream socketIn; try { socketIn = new DataInputStreamconnectionToLocalServer.getInputStream ; } catch IOException e { throw new ServletServerExceptionerror 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 ServletServerExceptionerror reading from server; } if line == null { throw new ServletServerException unexpected EOF reading server response; } if line.toLowerCase .startsWithkey { responseContentLength = Integer.parseIntline.substringkey. length.trim ; contentLengthFound = true; } } while line.length = 0 line.charAt0 = \r line.charAt0 = \n; if contentLengthFound || responseContentLeng th 0 throw new ServletServerExceptionmissing or invalid content length in server response; buffer = new byte[responseContentLength]; try { socketIn.readFullybuffer; } catch EOFException e { throw new ServletServerExceptionunexpected EOF reading server response; } catch IOException e { throw new ServletServerExceptionerror reading from server; } response.setStatusHttpServletResponse.SC_OK; response.setContentTypeapplicationoctet-stream; response.setContentLengthbuffer.length; try { OutputStream out = response.getOutputStream ; out.writebuffer; out.flush ; } catch IOException e { throw new ServletServerExceptionerror writi ng response; } } } There are three important points to notice about ServletForwarder . First, it uses sockets and streams to forward the body of the HTTP message to the RMI server and then simply pipes what the RMI server returns back into HttpServletResponse s output stream. There is no magical or mysterious code here, and HTTP tunneling involves nothing that you hadnt seen by the end of Chapt er 2 . The second important point is that ServletForwarder forwards the message without demarshalling the data or understanding the message at all. Even though the body of the HTTP request contains serialized instances of classes, ServletForwarder doesnt need to have the classes on its classpath or treat the serialized instances as anything other than bytes to be copied from one stream to another. This means that HTTP tunneling is easy to deploy and doesnt need to be updated as the application evolves. The final point is that, all tolled, this is really a simple piece of code that takes advantage of a lot of existing infrastructure. You can download the Apache web server available at ht t p: w w w .apache.or g , install the Tomcat servlet engine available at ht t p: j akar t a.apache.or g , and, in the course of an afternoon, circumvent a firewall that took experienced systems administrators months to configure and install.

22.5 Modifying the Tunneling Mechanism