- 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