tomcat-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Jochen Wiedmann <jochen.wiedm...@softwareag.com>
Subject How to write a request decompressing input filter?
Date Wed, 18 May 2005 08:26:51 GMT

Hi,

while writing an input filter, that decompresses its input, I have
encountered a problem. My filter (see complete source below) contains
roughly the following code:

  private HttpServletRequest
      getServletRequest(final HttpServletRequest pRequest) {
	  String contentEncoding =
              pRequest.getHeader("Content-Encoding");
	  if (!isUsingGzipEncoding(contentEncoding)) {
              return pRequest;
          }
          return new HttpServletRequestWrapper(pRequest) {
	      public ServletInputStream getInputStream()
                      throws IOException {
                  final InputStream rin = pRequest.getInputStream();
		  final InputStream in = new GZIPInputStream(rin);
                  return new ServletInputStream(){
                      public int read() throws IOException {
                          return in.read();
		      }
		      public void close() throws IOException {
			  in.close();
		      }
		  };
	     }
	     public BufferedReader getReader() throws IOException {
                final String enc = getCharacterEncoding();
                final InputStream istream = getInputStream();
                final Reader r = new InputStreamReader(istream, enc);
		return new BufferedReader(istream);
             }
	  };
      }

This works fine in most cases, with one important exception: If the
request is using the POST method with a content type
"x-www-form-urlencoded", then the following occurs:

* My Servlet invokes getParameterNames() on my request wrapper.
* The request wrapper invokes getParameterNames() on the RequestFacade.
* The request facade invokes getParameterNames() on the Request object.
* Which finally invokes getInputStream(), but not on my request wrapper,
  but on the Request object. In other words, the compressed input stream
  is read.


Any suggestions for a possible workaround?


Regards,

Jochen



package de.sag.dms.common.servlet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;


/** <p>The <code>GzipFilter</code> is a servlet filter, that enables use
of
 * gzip compression for both incoming and outgoing data.</p>
 * <p>Servlet filters can be added to arbitrary servlets. Whether they are
 * active or not, is a matter of configuration in the <code>web.xml</code>
 * file. For example, to activate the gzip filter for the URI /DSIServlet,
 * you would add the following lines to web.xml:</p>
 * <pre>
 *   &lt;filter&gt;
 *     &lt;filter-name&gt;GZIPFilter&lt;/filter-name&gt;
 *
&lt;filter-class&gt;de.sag.dms.common.servlet.GzipFilter&lt;/filter-class&gt;
 *   &lt;/filter&gt;
 *
 *   &lt;filter-mapping&gt;
 *     &lt;filter-name&gt;GZIPFilter&lt;/filter-name&gt;
 *     &lt;url-pattern&gt;/DSIServlet&lt;/url-pattern&gt;
 *   &lt;/filter-mapping&gt;
 * </pre>
 * <p>Note, that the context name will always be prepended to the URI.
 * In other words, if your web application is accessible below
 * "/DSI", then the actual URI for the above mapping would be
 * "/DSI/DSIServlet".</p>
 * <p>If a client wants to send compressed data, then it should
 * behave according to section 14.11 of RFC 2616: The client <em>must</em>
 * compress the whole body and it <em>must</em> set the
 * <em>request header</em> * "Content-Encoding" to a proper value,
 * typically "gzip". See
 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">
 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html</a> for details
 * on RFC 2616.</p>
 * <p>If a client wants the server to reply with compressed data, then
 * it should again follow RFC 2616 and set the <em>request header</em>
 * "Accept-Encoding". The value will typically be a comma separated list
 * of words. The word "gzip" indicates, that the client would like to
 * accept gzip compressed data. For example, browsers will typically
 * send "gzip, deflate".</p>
 * <p>If the server detects, that the client wants a compressed response,
 * then it will reply by setting the <em>response header</em>
 * "Content-Encoding" to the value "gzip". Consequently, the
 * whole response body will be compressed using gzip.</p>
 * <p>To verify, whether the filter works, do the following: Create a
 * request file "/tmp/request.txt", for example like this:</p>
 * <pre>
 * -----------------------------21
 * Content-Disposition: form-data; name="Command"
 *
 * Ping
 * -----------------------------21
 * Content-Disposition: form-data; name="password"
 * DsiHero9)
 * -----------------------------21
 * Content-Disposition: form-data; name="cpassword"
 *
 * -----------------------------21
 * Content-Disposition: form-data; name="user"
 *
 * root
 * -----------------------------21--
 * </pre>
 * Send the file to the server with a proper utility. For example,
 * use the perl utility "GET":
 * <pre>
 *   cat /tmp/request.txt |
 *   GET -m POST
 *     -c "multipart/form-data; boundary=-----------------------------21"
 *     http://127.0.0.1:8080/DSI/DSIServlet
 * </pre>
 * <p>(The above command is edited for readability and must be entered
 * on a single line, of course.) The program should display a response
 * like the following:</p>
 * <pre>
 *   &lt;result success="YES" resultcode="0"&gt;
 *     &lt;resulttext&gt;DSI-Server alive
 *       &lt;version patchlevel='0'
date='2005-04-29'&gt;4.2.1&lt;/version&gt;
 *       &lt;specification date='2005-04-15'&gt;4.2.1&lt;/specification&gt;
 *       &lt;api-version date='2005-04-15'&gt;3.0.21&lt;/api-version&gt;
 *       &lt;timestamp ms='1114587247628' date='2005-04-27 09:34:07
CEST'/&gt;
 *     &lt;/resulttext&gt;
 *   &lt;/result&gt;
 * </pre>
 * <p>Now modify the command to request gzip encoding:</p>
 * <pre>
 *   cat /tmp/request.txt |
 *   GET -m POST -H "Accept-Encoding: gzip"
 *     -c "multipart/form-data; boundary=-----------------------------21"
 *     http://127.0.0.1:8080/DSI/DSIServlet |
 *   gzip -cd
 * </pre>
 * <p>The result should remain the same.</p>
 */
public class GzipFilter implements Filter {
	private FilterConfig config;

	public void init(FilterConfig pConfig) throws ServletException {
		config = pConfig;
	}
	public void destroy() {
		config = null;
	}

	private HttpServletRequest getServletRequest(final HttpServletRequest
pRequest) {
		if
(!HttpUtil.isUsingGzipEncoding(pRequest.getHeader("Content-Encoding"))) {
			return pRequest;
		}
		return new HttpServletRequestWrapper(pRequest){
			public int getContentLength() {
				/* Even if we have a content length, it is now invalid,
				 * because it would be the content length of the compressed
				 * request.
				 */
				return -1;
			}
			public ServletInputStream getInputStream() throws IOException {
				final InputStream in = new GZIPInputStream(pRequest.getInputStream());
				return new ServletInputStream(){
					public int read() throws IOException {
						return in.read();
					}
					public void close() throws IOException {
						in.close();
					}
				};
			}
			public BufferedReader getReader() throws IOException {
				return new BufferedReader(new InputStreamReader(getInputStream(),
getCharacterEncoding()));
			}
		};
	}

	private HttpServletResponse getServletResponse(final HttpServletRequest
pRequest,
												   final HttpServletResponse pResponse) {
		if
(!HttpUtil.isUsingGzipEncoding(pRequest.getHeader("Accept-Encoding"))) {
			return pResponse;
		}
		return new HttpServletResponseWrapper(pResponse){
			boolean created;
			public ServletOutputStream getOutputStream() throws IOException {
				if (created) {
					throw new IllegalStateException("Already created");
				}
				created = true;
				pResponse.setHeader("Content-Encoding", "gzip");
				final OutputStream gzo = new
GZIPOutputStream(pResponse.getOutputStream());
				return new ServletOutputStream(){
					public void write(int b) throws IOException {
						gzo.write(b);
					}
					public void flush() throws IOException {
						gzo.flush();
					}
					public void close() throws IOException {
						gzo.close();
					}
				};
			}
			public PrintWriter getWriter() throws IOException {
				return new PrintWriter(getOutputStream());
			}
			public void setContentLength(int pArg) {
				/* Whatever the content length, we ignore it: It would be
				 * the content length of the uncompressed stream, which
				 * is currently meaningless.
				 */
				pArg = -1;	// This line and the following just to suppress
				pResponse.setContentLength(pArg); // a "not used" warning.
			}
		};
	}

	public void doFilter(ServletRequest pRequest, ServletResponse pResponse,
						 FilterChain pChain) throws IOException, ServletException {
		if (config == null) { return; }
		HttpServletRequest req = (HttpServletRequest) pRequest;
		HttpServletResponse res = (HttpServletResponse) pResponse;
		req = getServletRequest(req);
		res = getServletResponse(req, res);
		pChain.doFilter(req, res);
	}
}


---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-user-help@jakarta.apache.org


Mime
View raw message