Return-Path: Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id CAA02390; Wed, 27 Jun 2001 02:31:41 -0400 (EDT) Received: from hermes.java.sun.com (hermes.java.sun.com [204.160.241.85]) by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id CAA11542 for ; Wed, 27 Jun 2001 02:31:40 -0400 (EDT) Message-Id: <200106270631.CAA11542@pacific-carrier-annex.mit.edu> Date: Tue, 26 Jun 2001 23:31:40 PDT From: "JDC Tech Tips" To: alexp@mit.edu Subject: JDC Tech Tips June 26, 2001 Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: Beyond Email 2.2 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, June 26, 2001. This issue covers two tips on servlet filters: * Improving Code Reuse With Servlet Filters * Using Filters To Modify The Server's Response In order to run the code in these tips you need access to a servlet engine that is compatible with the Java(tm) Servlet 2.3 Specification, now in public final draft 2. The instructions assume you are using the reference implementation, Tomcat 4.0 beta 5 or later. You can download Tomcat at http://jakarta.apache.org/tomcat/index.html. These tips were developed using Java 2 SDK, Standard Edition, v 1.3. This issue of the JDC Tech Tips is written by Stuart Halloway, a Java specialist at DevelopMentor (http://www.develop.com/java). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2001/tt0626.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - IMPROVING CODE REUSE WITH SERVLET FILTERS There are several ways to reuse code in a servlet environment. For example, you might use inheritance to reuse the code in the YourCoolServlet class from a new, improved YourCoolerServlet. Also, the Java Servlet specification defines natural mechanisms to chain execution from one servlet to another. You can use the java.servlet.RequestDispatcher's forward or include methods to transfer control from one servlet to another. While all of these techniques have their use, they also suffer from some disadvantages. They require you to explicitly wire servlets together in code, which means that you must plan for reuse during development. With the forward and include methods, you cede too much control to the downstream servlet -- you might not be able to modify the content that a downstream servlet writes into the response. With the Java Servlet 2.3 API, you now have another option: use servlet filters. Servlet filters are a new feature in the Java Servlet 2.3 API. They allow you to transform the content of requests and responses. Each resource provided by the servlet engine can be hidden behind an arbitrary chain of filters. As a request passes through, each filter gets a chance to view, and optionally modify, the behavior of the underlying resource. Best of all, setting up a chain of servlet filters is an administrative task that requires no changes to existing code. Consider a simple use of filters where an administrator has installed two filters in front of some resource. Each filter can analyze the request, and decide whether to modify the request before passing it to the next object in the "filter chain." Also, each filter can modify the response to be sent back to the client. The filter can do this either before or after calling the next object in the chain. The resource at the end of the chain could be a servlet, JSP, static content, or any other end point that your engine provides. To see the power of servlet filters, consider another scenario. Imagine that you're working for RockSolidBids, an online bidding system. You arrive at work one morning to discover that one of your partners, FlakyNetworks Inc., is having network problems. Unfortunately, your own application contains reams of code that connects to FlakyNetworks. You do not have time to fix the problem in each individual servlet. The situation is getting critical -- the failures and retries attempting to connect to FlakyNetworks are hogging bandwidth and bringing down the rest of your customers. Your mission: add a filter in front of all of your servlets to temporarily reject all requests involving FlakyNetworks Inc. With a servlet filter, you can easily solve this problem without touching existing code. First, define a RequestBlocker class that implements the Filter interface: package com.develop.filters; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class RequestBlocker implements Filter { private Properties blockThese = new Properties(); { blockThese.setProperty("company", "FlakyNetworks"); } private ServletContext ctx; public void init(javax.servlet.FilterConfig filterConfig) throws ServletException { ctx = filterConfig.getServletContext(); ctx.log("Filter " + filterConfig.getFilterName() + " initialized."); } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws java.io.IOException, ServletException { HttpServletRequest hsr = (HttpServletRequest)servletRequest; for (Enumeration en = blockThese.keys(); en.hasMoreElements();) { String name = (String)en.nextElement(); String param = hsr.getParameter(name); if ((param != null) && blockThese.get(name).equals(param)) { HttpServletResponse hResp = (HttpServletResponse) servletResponse; String msg = "Request for " + name + " " + param + " rejected."; hResp.sendError(hResp.SC_SERVICE_UNAVAILABLE, msg); ctx.log(msg); return; } } filterChain.doFilter(servletRequest, servletResponse); } public void destroy() { } } The Filter interfaces defines three methods: o init. This method is called before the servlet engine begins using the filter. In this example the init method simply stores the ServletContext for use later and logs that it completed successfully. o destroy. This method is called before the engine removes a filter from service. If you need to clean up filter-specific resources, you can do that with the destroy method. o doFilter. This method is the meat of the filter. Inside this method, you have access to the request and response objects, just as you would in a normal servlet's doGet or doPost method. You can query or modify these objects as needed. Then, you can forward the request to the next filter in the chain (or to the servlet if this is deployed as the last filter) by calling filterChain.doFilter. The RequestBlocker's implementation of doFilter is very simple. It loops over a set of key/value pairs defined by the blockThese instance (that is, "company", "FlakyNetworks"). If any request parameters match the key/value pair, the filter uses the response object to send an HTTP 503 error (SC_SERVICE_UNAVAILABLE) back to the client. Because the filter has dealt completely with the request at this point, it does not call filterChain.doFilter. If the request does not contain any disallowed parameters, the RequestBlocker does nothing and passes the request to the next filter/servlet. To use the RequestBlocker, you must deploy it into a servlet engine. The instructions that follow assume that you are deploying into Tomcat's examples application. The instructions for other engines should be similar. 1. Compile the RequestBlocker with servlet.jar on your class path. Tomcat keeps servlet.jar at common/lib. Copy the RequestBlocker.class file to /{yourTomcat}/webapps/examples/WEB-INF/classes/com/develop/filters 2. Open the deployment descriptor /{yourTomcat}/webapps/examples/WEB-INF/web.xml 3. Define the filter by adding this XML fragment at the bottom of the list of filters elements: Request Blocker com.develop.filters.RequestBlocker This associates the name "Request Blocker" with your filter. 4. Tell the engine when to use the filter by adding this XML fragment to the bottom of the filter-mappings: Request Blocker /* This tells the engine to invoke the filter for all URLs that map to the application. To start Tomcat, run bin\startup from your Tomcat root directory. After Tomcat starts, use the browser of your choice to navigate into the examples application. If you have not changed Tomcat's default port, you can use the URL http://localhost:8080/examples/servlets/index.html. The application should run normally. Try using various example servlets. Each of these accesses is filtered, but because none of the requests mentions FlakyNetworks, the filter is entirely transparent. What happens if you edit a URL such that it mention FlakyNetworks? Try the following URL (all on one line): http://localhost:8080/examples/servlet/ HelloWorldExample?company=FlakyNetworks Now the filter takes action, and you should see browser message 503, "Request for company FlakyNetworks rejected." This simple filter provides an excellent example of code reuse. You are reusing all of the servlets in your system, and you have changed their behavior: they now reject calls to FlakyNetworks. However, this kind of reuse does not require inheritance or delegation to specific servlets. In fact, you do not even have to assume that there is a servlet at the end of the chain. The filter works just as well for any resource, even simple HTML pages. For example, try the HTML page http://localhost:8080/examples/?company=FlakyNetworks. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING FILTERS TO MODIFY THE SERVER'S RESPONSE The RequestBlocker example in the tip "Improving Code Reuse With Servlet Filters" used filters to reject certain requests. A more important use of filters is to modify the response sent back from the server. The XSLTFilter class shown below automatically performs a transform on an XML document returned by a server, rendering the document into HTML before returning it to the caller. //class com.develop.filters.XSLTFilter package com.develop.filters; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; public class XSLTFilter implements Filter { private ServletContext ctx; private String xslt; private TransformerFactory tf = TransformerFactory.newInstance(); private Transformer xform; private static class ByteArrayServletStream extends ServletOutputStream { ByteArrayOutputStream baos; ByteArrayServletStream(ByteArrayOutputStream baos) { this.baos = baos; } public void write(int param) throws java.io.IOException { baos.write(param); } } private static class ByteArrayPrintWriter { private ByteArrayOutputStream baos = new ByteArrayOutputStream(); private PrintWriter pw = new PrintWriter(baos); private ServletOutputStream sos = new ByteArrayServletStream(baos); public PrintWriter getWriter() { return pw; } public ServletOutputStream getStream() { return sos; } byte[] toByteArray() { return baos.toByteArray(); } } public void init(FilterConfig filterConfig) throws ServletException { ctx = filterConfig.getServletContext(); xslt = filterConfig.getInitParameter("xslt"); ctx.log("Filter " + filterConfig.getFilterName() + " using xslt " + xslt); try { xform = tf.newTransformer(new StreamSource( ctx.getResourceAsStream(xslt))); } catch (Exception e) { ctx.log("Could not intialize transform", e); throw new ServletException( "Could not initialize transform", e); } } public String httpReqLine(HttpServletRequest req) { StringBuffer ret = req.getRequestURL(); String query = req.getQueryString(); if (query != null) { ret.append("?").append(query); } return ret.toString(); } public String getHeaders(HttpServletRequest req) throws IOException { Enumeration en = req.getHeaderNames(); StringBuffer sb = new StringBuffer(); while (en.hasMoreElements()) { String name = (String) en.nextElement(); sb.append(name).append(": ").append( req.getHeader(name)).append("\n"); } return sb.toString(); } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws java.io.IOException, ServletException { HttpServletRequest hsr = (HttpServletRequest)servletRequest; final HttpServletResponse resp = ( HttpServletResponse)servletResponse; ctx.log("Accessing filter for " + httpReqLine(hsr) + " " + hsr.getMethod()); final ByteArrayPrintWriter pw = new ByteArrayPrintWriter(); final boolean[] xformNeeded = new boolean[1]; HttpServletResponse wrappedResp = new HttpServletResponseWrapper(resp) { public PrintWriter getWriter() { return pw.getWriter(); } public ServletOutputStream getOutputStream() { return pw.getStream(); } public void setContentType(String type) { if (type.equals("text/xml")) { ctx.log("Converting xml to html"); resp.setContentType("text/html"); xformNeeded[0] = true; } else { resp.setContentType(type); } } }; filterChain.doFilter(servletRequest, wrappedResp); byte[] bytes = pw.toByteArray(); if (bytes == null || (bytes.length == 0)) { ctx.log("No content!"); } if (xformNeeded[0] == true) { try { xform.transform(new StreamSource( new ByteArrayInputStream(bytes)), new StreamResult(resp.getOutputStream())); ctx.log("XML -> HTML conversion completed"); } catch (Exception e) { throw new ServletException( "Unable to transform document", e); } } else { resp.getOutputStream().write(bytes); } } public void destroy() { ctx.log("Destroying filter..."); } } The XSLTFilter class is a good deal more complex than the RequestBlocker example, but the single critical method is doFilter. Instead of passing the response object to the next filter in the chain, XSLTFilter's call to doFilter specifies a customized response object named wrappedResp. To make it easy to replace the response object, the filter architecture provides a helper class, named HttpServletResponseWrapper, that wraps the original response object, and simply passes through every method call. XSLTFilter creates an anonymous subclass of HttpServletResponseWrapper, overriding three methods: getOutputStream, getWriter, and setContentType. The getOutputStream and getWriter methods use an instance of the nested helper class ByteArrayPrintWriter, named pw. When a downstream filter or servlet writes into the "response," it actually writes into the instance of pw. The setContentType method checks to see if the content being returned is "text/xml". If a downstream servlet or filter tries to set the content type to "text/xml", the overridden setContentType changes it to "text/html" instead, and sets a flag, xformNeeded[0], indicating that the transform needs to run. After calling doFilter, XSLTFilter checks to see if it needs to transform the response. If it does, it takes the downstream response from pw, and transforms it into the "real" response, resp. Of course, resp might not be the "real" response either, because XSLTFilter might be downstream from yet another filter. The transform is performed using XSLT, which is loaded using the Java API for XML Parsing (JAXP) TransformerFactory class. Setting up the transform illustrates another filter feature: initialization parameters. The XSLTFilter expects to be configured with an initialization parameter named "xslt" that specifies which transform to run. To see XSLTFilter in action, execute the following steps: 1. You will need an XSLT processor. This tip was tested with the open source processor Xalan-Java version 2.1.0, which you can download from http://xml.apache.org/xalan-j/index.html. 2. Assuming you are working in the examples application of Tomcat, add xerces.jar and xalan.jar to {yourTomcat}/webapps/examples/lib 3. Compile XSLTFilter with servlet.jar and jaxp.jar on your class path. Both these jar files can be found in the Tomcat installation. Copy the XSLTFiler.class to {yourTomcat}/webapps/examples/WEB-INF/classes/com/develop/filters 4. Configure the filter. You will need to add two XML fragments to {yourTomcat}/webapps/examples/WEB-INF/web.xml XSLT Filter com.develop.filters.XSLTFilter xslt /xform2.xsl XSLT Filter /* 5. Make sure that your servlet engine is configured to set the content type for XML files. In Tomcat you will edit the {yourTomcat}/conf/web.xml file and add the XML fragment xml text/xml 6. Install some XML and XSL files. The configuration step above assumes that you are using xform2.xsl and Index.xml as shown below. Copy the files to {YourTomcat}/webapps/examples

JDC Tech Tips Archive



HTML | TEXT |
7. Restart Tomcat. Using your browser, navigate to http://localhost:8080/examples/Index.xml. The filter will automatically transform this document into HTML. 8. Try other XML and XSL documents. Do not forget to restart Tomcat if you change the init-param for the filter. To learn more about servlet filters, see the Java Servlet 2.3 Specification at http://java.sun.com/products/servlet/index.html For an overview of new features in the Java Servlet 2.3 Specification, see the article "Newer is Better" by Kevin Jones at http://www.java-pro.com/upload/free/Features/Javapro/2001/07jul01/kj0107/kj0107-1.asp . . . . . . . . . . . . . . . . . . . . . . . - NOTE Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun Microsystems(tm) purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click the Update button. As of May 22, 2001, Sun Microsystems updated its Privacy Policy (http://sun.com/privacy) to give you a better understanding of Sun's Privacy Policy and Practice. If you have any questions, contact privacy@sun.com. - SUBSCRIBE To subscribe to a JDC newsletter mailing list, go to the Subscriptions page (http://developer.java.sun.com/subscription/), choose the newsletters you want to subscribe to, and click Update. - FEEDBACK Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com - ARCHIVES You'll find the JDC Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2001 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html - LINKS TO NON-SUN SITES The JDC Tech Tips may provide, or third parties may provide, links to other Internet sites or resources. Because Sun has no control over such sites and resources, You acknowledge and agree that Sun is not responsible for the availability of such external sites or resources, and does not endorse and is not responsible or liable for any Content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such Content, goods or services available on or through any such site or resource. JDC Tech Tips June 26, 2001 Sun, Sun Microsystems, Java, and Java Developer Connection are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. To use our one-click unsubscribe facility, select the following URL: http://hermes.java.sun.com/unsubscribe?3293992085908251857