- From: Roland Mainz <Roland.Mainz@informatik.med.uni-giessen.de>
- Date: Thu, 06 Apr 2000 17:51:50 +0200
- To: W3 Jigsaw Mailinglist <www-jigsaw@w3.org>
- CC: Ralf Schweiger <Ralf.Schweiger@informatik.med.uni-giessen.de>
- Message-Id: <38ECB296.4E829AB1@informatik.med.uni-giessen.de>
Roland Mainz wrote: > > > A coworker found yesterday a nasty performance problem in jigsaw > > > (jigsaw 2.0.4 with Tomcat 3.1 M1): > > > A servlet receiving a large HTTP POST request consumes a lot CPU time > > > (in our case it needs ~2-3 secs) in the first > > > HttpServletRequest.getParameter() call. > > > Any idea where the problem sits and how to solve this (without buing a > > > UltraSPARC-4500 to power-out the problem =:-) ? > > > > Well, the parsing is done in org.w3c.jigsaw.forms.URLDecoder so the "problem" should > > sits there. But I don't see anything that could slow down the request handling, this should > > be a really really big POST! what's its size? > > Uhm, don't know - AFAIK it has ~100 entries each ~30 bytes long (unencoded). > > After seeking and travelling around the sources: > 1. JigsawHttpServletRequest.java's prepareQueryParameters() request.getInputStream() may be > wrapped by a BufferedInputStream ... > 2. What about using a BufferedReader in general within URLDecoder (which would obsoltete [1]) > ? > 3. Is there a way to count the number of bytes in the stream (via req ?) ? Maybe it's possible > to calculate the size of URLDecoder's "buffer" array (which would avoid some > reallocations)... !? > 4. If [3] isn't possible, what about setting URLDecoder's "buffer" array to an initial size of > 1024 bytes ? OK, we tested a combination of [2] and [4] (with 256 bytes as the initial size) which drastically improved performance (source attached). It seems that most performance win is got from buffering the URLDecoder input... Any comments ? ---- Bye, Roland -- __ . . __ (o.\ \/ /.o) Roland.Mainz@informatik.med.uni-giessen.de \__\/\/__/ gisburn@informatik.med.uni-giessen.de /O /==\ O\ MPEG specialist, C&&JAVA&&Sun&&Unix programmer (;O/ \/ \O;) TEL +49 641 99-13193 FAX +49 641 99-41359
// URLDecoder.java // $Id: URLDecoder.java,v 1.8 1999/03/17 18:04:03 bmahe Exp $ // (c) COPYRIGHT MIT and INRIA, 1996. // Please first read the full copyright statement in file COPYRIGHT.html package org.w3c.jigsaw.forms ; import java.io.* ; import java.util.Hashtable ; import java.util.Enumeration ; import org.w3c.jigsaw.* ; /** * Form data decoder. * This class takes an InputStream and decodes it in compliance to the * <b>application/x-www-form-urlencoded</b> MIME type. */ public class URLDecoder { static { System.err.println( "modified URLDecoder used." ); } public final static String EMPTY_VALUE = "" ; int ch = -1 ; Hashtable values = null ; byte buffer[] = new byte[256] ; int bsize = 0 ; Reader in = null ; boolean overide = true ; private void append (int c) { if ( bsize+1 >= buffer.length ) { byte nb[] = new byte[buffer.length*2] ; System.arraycopy (buffer, 0, nb, 0, buffer.length) ; buffer = nb ; } buffer[bsize++] = (byte) c ; } /** * Get an enumeration of the variable names. * @return An enumeration continaing one element per key. */ public Enumeration keys() { return values.keys() ; } /** * Define a new variable in this data set. * @param name The name of the variable. * @param value Its value. */ protected void addVariable (String var, String val) { if ( overide ) { values.put (var, val) ; } else { Object value = values.get (var) ; if ( value == null ) { values.put (var, val) ; } else if ( value instanceof String[] ) { String olds[] = (String[]) value ; String vals[] = new String[olds.length+1] ; System.arraycopy (olds, 0, vals, 0, olds.length) ; vals[olds.length] = val ; values.put (var, vals) ; } else if ( value instanceof String ) { String vals[] = new String[2] ; vals[0] = (String) value ; vals[1] = val ; values.put (var, vals) ; } } } /** * Get the values of the variable, as an array. * Use this method when you have turned off the <em>overide</em> flag * in the constructor of this object. This will always return either an * array of Strings or <strong>null</strong>. * <p>I use this in the PICS label bureau, and I pretty sure this is not a * good reason to have it here. * @param name The name of the variable to look for. * @return An String[] having one entry per variable's value, or <strong> * null</strong> if none was found. */ public String[] getMultipleValues (String name) { if ( overide ) throw new RuntimeException (this.getClass().getName() + "[getMultipleValues]: " + " overide not set !") ; Object value = values.get (name) ; if ( value instanceof String[] ) { return (String[]) value ; } else { String vals[] = new String[1] ; vals[0] = (String) value ; values.put (name, vals) ; return vals ; } } /** * Get the value of a variable. * If you have allowed the decoder to accumulate multiple values for the * same variable, this method casts of the value to a String may fail * <em>at runtime</em>. * @param name The name of the variable whose value is to be fetched. * @return Its values, which is always provided as a String, or null. */ public String getValue (String name) { Object value = values.get(name) ; if ( (value != null) && ! (value instanceof String) ) throw new RuntimeException (this.getClass().getName() + "[getValue]:" + " use getMultipleValues in:\n\t" + name + " " + value) ; return (String) value ; } /** * Parse our input stream following the * <b>application/x-www-form-urlencoded</b> specification. * @return The raw bindings obtained from parsing the stream, as a * Hashtable instance. * @exception IOException When IO error occurs while reading the stream. * @exception URLDecoderException If the format is invalid. */ public Hashtable parse () throws IOException, URLDecoderException { String key = null ; read_loop: ch = in.read() ; while ( true ) { switch (ch) { case '+': append (' ') ; break ; case '%': int hi, lo ; if ((hi = ch = in.read()) == -1) throw new URLDecoderException ("Invalid escape seq.") ; if ((lo = ch = in.read()) == -1) throw new URLDecoderException ("Invalid escape seq.") ; hi = (Character.isDigit((char) hi) ? (hi - '0') : 10 + (Character.toUpperCase((char) hi) - 'A')) ; lo = (Character.isDigit((char) lo) ? (lo - '0') : 10 + (Character.toUpperCase((char) lo) - 'A')) ; append ((char)(((byte) lo) | (((byte) hi) << 4))) ; break ; case '&': if ( key == null ) { // We only get a simple key (with no value) addVariable(new String(buffer,0,bsize), EMPTY_VALUE); bsize = 0 ; } else { addVariable (key, new String (buffer, 0, bsize)) ; key = null ; bsize = 0 ; } break ; case ';': // HTML4.0: appendix b2.2: use of ";" in place of "&" if ( key == null ) { // We only get a simple key (with no value) addVariable(new String(buffer,0,bsize), EMPTY_VALUE); bsize = 0 ; } else { addVariable (key, new String (buffer, 0, bsize)) ; key = null ; bsize = 0 ; } break ; case '=': if ( key != null ) { append(ch); } else { key = new String (buffer, 0, bsize) ; bsize = 0 ; } break ; case -1: // Same as '&', except that we return if ( key == null ) { // We only get a simple key (with no value) addVariable(new String(buffer,0,bsize), EMPTY_VALUE); bsize = 0 ; } else { addVariable (key, new String (buffer, 0, bsize)) ; key = null ; bsize = 0 ; } return values ; default: append (ch) ; break ; } ch = in.read() ; } } /** * Create an URLDecoder for the given stream. * @param in The input stream to be parsed. * @param list Tells how to handle multiple settings of the same variable. * When <strong>false</strong>, mutiple settings to the same variable * will accumulate the value into an array, returned by getValue(). * Otherwise, the last assignment will overide any previous assignment. */ public URLDecoder (Reader in, boolean overide) { if( !(in instanceof BufferedReader) ) { in = new BufferedReader( in ); } this.values = new Hashtable (23) ; this.in = in ; this.ch = -1 ; this.overide = overide ; } /** * Create an URLDecoder for the given stream. * @param in The input stream to be parsed. * @param list Tells how to handle multiple settings of the same variable. * When <strong>false</strong>, mutiple settings to the same variable * will accumulate the value into an array, returned by getValue(). * Otherwise, the last assignment will overide any previous assignment. */ public URLDecoder (Reader in) { this(in, true); } /** * Create an URLDecoder for the given stream. * @param in The input stream to be parsed. * @param list Tells how to handle multiple settings of the same variable. * When <strong>false</strong>, mutiple settings to the same variable * will accumulate the value into an array, returned by getValue(). * Otherwise, the last assignment will overide any previous assignment. */ public URLDecoder (InputStream in, boolean overide) { if( in instanceof BufferedInputStream ) { this.in = new InputStreamReader(in) ; } else { this.in = new BufferedReader( new InputStreamReader(in) ); } this.values = new Hashtable (23) ; this.ch = -1 ; this.overide = overide ; } /** * Create an URLDecoder for the given stream. * Default constructor, which will keep track only of the last setting * of the same variable (if ever it gets assigned multiply). * @param in The input stream to be parsed. */ public URLDecoder (InputStream in) { this (in, true) ; } }
Received on Thursday, 6 April 2000 11:51:58 UTC