Hi David,
David Daney wrote:
> David Daney wrote:
>
>> PR libgcj/26487 shows problems with our existing header handling.
>>
>> I hacked up the attached patch, which needs more testing (please test
>> it!)
>>
>> The basic problem is that the headers were being held in a map which
>> scrambled them up if there were more than one header of the same name.
>>
>> My change was to just hold the headers in a list. This makes some
>> searching operations less efficient, but seems the best way to keep
>> the headers from being combined.
>>
> New version of the patch. Just cleaned up and javadocs added.
>
> I guess I will commit it tomorrow if no one objects.
>
> 2006-03-02 David Daney <[EMAIL PROTECTED]>
>
> * gnu/java/net/protocol/http/HTTPURLConnection.java
> (getRequestProperties): Rewrote.
> (addRequestProperty): Rewrote.
> (getHeaderFields): Rewrote.
> (getHeaderField): Rewrote.
> (getHeaderFieldKey): Rewrote.
> (getHeaderField): Removed useless cast.
> * gnu/java/net/protocol/http/Headers.java: Entire class rewritten.
> * gnu/java/net/protocol/http/Request.java (dispatch): Use new
> Headers
> interface.
> (notifyHeaderHandlers): Use new Headers interface.
>
Nice to see you have removed that now useless inner Header class. This was one
of the comments I wanted to make. I worked this day on mauve testcases for this
rewrite.
These exposed two small bugs in Headers.java:
> Index: gnu/java/net/protocol/http/Headers.java
> ===================================================================
> RCS file:
> /sources/classpath/classpath/gnu/java/net/protocol/http/Headers.java,v
> retrieving revision 1.6
> diff -c -p -r1.6 Headers.java
> *** gnu/java/net/protocol/http/Headers.java 2 Mar 2006 00:26:57 -0000
> 1.6
> --- gnu/java/net/protocol/http/Headers.java 2 Mar 2006 20:40:32 -0000
[...]
> !
> ! /**
> ! * Get a new Map containing all the headers. The keys of the Map
> ! * are Strings (the header names). The values of the Map are
> ! * unmodifiable Lists containing Strings (the header values).
> ! *
> ! * <p>
> ! *
> ! * The returned map is modifiable. Changing it will not effect this
> ! * collection of Headers in any way.
> ! *
> ! * @return a Map containing all the headers.
> ! */
> ! public Map getAsMap()
> {
> ! LinkedHashMap m = new LinkedHashMap();
> ! for (Iterator it = headers.iterator(); it.hasNext(); )
> {
> ! HeaderElement e = (HeaderElement)it.next();
> ! String k = e.name.toLowerCase();
No lowercase here. Otherwise keys with uppercase names won't be found.
String k = e.name;
> ! ArrayList l = (ArrayList)m.get(k);
> ! if (l == null)
> ! {
> ! l = new ArrayList(1);
> ! l.add(e.value);
> ! m.put(k, l);
> ! }
> ! else
> ! l.add(e.value);
The second value must be added before. The test show
that SUN always adds the last received value for a
key first.
l.add(0, e.value);
> }
> ! for (Iterator it = m.entrySet().iterator(); it.hasNext(); )
> {
> ! Map.Entry me = (Map.Entry)it.next();
> ! ArrayList l = (ArrayList)me.getValue();
> ! me.setValue(Collections.unmodifiableList(l));
> }
> + return m;
> + }
> +
With these two tests all testcases here pass.
Updated diff -u patch attached.
Wolfgang
Index: gnu/java/net/protocol/http/Headers.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/net/protocol/http/Headers.java,v
retrieving revision 1.6
diff -u -r1.6 Headers.java
--- gnu/java/net/protocol/http/Headers.java 2 Mar 2006 00:26:57 -0000 1.6
+++ gnu/java/net/protocol/http/Headers.java 2 Mar 2006 21:18:28 -0000
@@ -1,5 +1,5 @@
/* Headers.java --
- Copyright (C) 2004 Free Software Foundation, Inc.
+ Copyright (C) 2004, 2006 Free Software Foundation, Inc.
This file is part of GNU Classpath.
@@ -44,124 +44,74 @@
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
import java.util.Map;
-import java.util.Set;
/**
- * A collection of HTTP header names and associated values.
- * Retrieval of values is case insensitive. An iteration over the keys
+ * A collection of HTTP header names and associated values. The
+ * values are [EMAIL PROTECTED] ArrayList ArrayLists} of Strings. Retrieval of
+ * values is case insensitive. An iteration over the collection
* returns the header names in the order they were received.
*
* @author Chris Burdess ([EMAIL PROTECTED])
+ * @author David Daney ([EMAIL PROTECTED])
*/
-public class Headers
- extends LinkedHashMap
+class Headers
{
-
+ /**
+ * A list of HeaderElements
+ *
+ */
+ private final ArrayList headers = new ArrayList();
+
static final DateFormat dateFormat = new HTTPDateFormat();
- static class Header
+ static class HeaderElement
{
+ String name;
+ String value;
- final String name;
-
- Header(String name)
+ HeaderElement(String name, String value)
{
- if (name == null || name.length() == 0)
- {
- throw new IllegalArgumentException(name);
- }
this.name = name;
+ this.value = value;
}
-
- public int hashCode()
- {
- return name.toLowerCase().hashCode();
- }
-
- public boolean equals(Object other)
- {
- if (other instanceof Header)
- {
- return ((Header) other).name.equalsIgnoreCase(name);
- }
- return false;
- }
-
- public String toString()
- {
- return name;
- }
-
- }
-
- static class HeaderEntry
- implements Map.Entry
- {
-
- final Map.Entry entry;
-
- HeaderEntry(Map.Entry entry)
- {
- this.entry = entry;
- }
-
- public Object getKey()
- {
- return ((Header) entry.getKey()).name;
- }
-
- public Object getValue()
- {
- return entry.getValue();
- }
-
- public Object setValue(Object value)
- {
- return entry.setValue(value);
- }
-
- public int hashCode()
- {
- return entry.hashCode();
- }
-
- public boolean equals(Object other)
- {
- return entry.equals(other);
- }
-
- public String toString()
- {
- return getKey().toString() + "=" + getValue();
- }
-
}
public Headers()
{
}
- public boolean containsKey(Object key)
- {
- return super.containsKey(new Header((String) key));
- }
-
- public Object get(Object key)
+ /**
+ * Return an Iterator over this collection of headers.
+ * Iterator.getNext() returns objects of type [EMAIL PROTECTED] HeaderElement}.
+ *
+ * @return the Iterator.
+ */
+ Iterator iterator()
{
- return super.get(new Header((String) key));
+ return headers.iterator();
}
-
+
/**
- * Returns the value of the specified header as a string.
+ * Returns the value of the specified header as a string. If
+ * multiple values are present, the last one is returned.
*/
public String getValue(String header)
{
- return (String) super.get(new Header(header));
+ for (int i = headers.size() - 1; i >= 0; i--)
+ {
+ HeaderElement e = (HeaderElement)headers.get(i);
+ if (e.name.equalsIgnoreCase(header))
+ {
+ return e.value;
+ }
+ }
+ return null;
}
/**
@@ -227,51 +177,62 @@
}
}
- public Object put(Object key, Object value)
- {
- return super.put(new Header((String) key), value);
- }
-
- public Object remove(Object key)
+ /**
+ * Add a header to this set of headers. If there is an existing
+ * header with the same name, it is discarded.
+ *
+ * @param name the header name
+ * @param value the header value
+ *
+ * @see #addValue
+ */
+ public void put(String name, String value)
{
- return super.remove(new Header((String) key));
+ remove(name);
+ headers.add(headers.size(), new HeaderElement(name, value));
}
- public void putAll(Map t)
+ /**
+ * Add all headers from a set of headers to this set. If any of the
+ * headers to be added have the same name as existing headers, the
+ * existing headers will be discarded.
+ *
+ * @param o the headers to be added
+ */
+ public void putAll(Headers o)
{
- for (Iterator i = t.keySet().iterator(); i.hasNext(); )
+ for (Iterator it = o.iterator(); it.hasNext(); )
{
- String key = (String) i.next();
- String value = (String) t.get(key);
- put(key, value);
+ HeaderElement e = (HeaderElement)it.next();
+ remove(e.name);
}
- }
-
- public Set keySet()
- {
- Set keys = super.keySet();
- Set ret = new LinkedHashSet();
- for (Iterator i = keys.iterator(); i.hasNext(); )
+ for (Iterator it = o.iterator(); it.hasNext(); )
{
- ret.add(((Header) i.next()).name);
+ HeaderElement e = (HeaderElement)it.next();
+ addValue(e.name, e.value);
}
- return ret;
}
- public Set entrySet()
+ /**
+ * Remove a header from this set of headers. If there is more than
+ * one instance of a header of the given name, they are all removed.
+ *
+ * @param name the header name
+ */
+ public void remove(String name)
{
- Set entries = super.entrySet();
- Set ret = new LinkedHashSet();
- for (Iterator i = entries.iterator(); i.hasNext(); )
+ for (Iterator it = headers.iterator(); it.hasNext(); )
{
- Map.Entry entry = (Map.Entry) i.next();
- ret.add(new HeaderEntry(entry));
+ HeaderElement e = (HeaderElement)it.next();
+ if (e.name.equalsIgnoreCase(name))
+ it.remove();
}
- return ret;
}
/**
- * Parse the specified input stream, adding headers to this collection.
+ * Parse the specified InputStream, adding headers to this collection.
+ *
+ * @param in the InputStream.
*/
public void parse(InputStream in)
throws IOException
@@ -333,18 +294,91 @@
}
}
- private void addValue(String name, String value)
+
+ /**
+ * Add a header to this set of headers. If there is an existing
+ * header with the same name, it is not effected.
+ *
+ * @param name the header name
+ * @param value the header value
+ *
+ * @see #put
+ */
+ public void addValue(String name, String value)
+ {
+ headers.add(headers.size(), new HeaderElement(name, value));
+ }
+
+ /**
+ * Get a new Map containing all the headers. The keys of the Map
+ * are Strings (the header names). The values of the Map are
+ * unmodifiable Lists containing Strings (the header values).
+ *
+ * <p>
+ *
+ * The returned map is modifiable. Changing it will not effect this
+ * collection of Headers in any way.
+ *
+ * @return a Map containing all the headers.
+ */
+ public Map getAsMap()
{
- Header key = new Header(name);
- String old = (String) super.get(key);
- if (old == null)
+ LinkedHashMap m = new LinkedHashMap();
+ for (Iterator it = headers.iterator(); it.hasNext(); )
{
- super.put(key, value);
+ HeaderElement e = (HeaderElement)it.next();
+ String k = e.name;
+ ArrayList l = (ArrayList)m.get(k);
+ if (l == null)
+ {
+ l = new ArrayList(1);
+ l.add(e.value);
+ m.put(k, l);
+ }
+ else
+ l.add(0, e.value);
}
- else
+ for (Iterator it = m.entrySet().iterator(); it.hasNext(); )
{
- super.put(key, old + ", " + value);
+ Map.Entry me = (Map.Entry)it.next();
+ ArrayList l = (ArrayList)me.getValue();
+ me.setValue(Collections.unmodifiableList(l));
}
+ return m;
+ }
+
+ /**
+ * Get the name of the Nth header.
+ *
+ * @param i the header index.
+ *
+ * @return the header name.
+ *
+ * @see #getHeaderValue
+ */
+ public String getHeaderName(int i)
+ {
+ if (i >= headers.size() || i < 0)
+ return null;
+
+ return ((HeaderElement)headers.get(i)).name;
+ }
+
+ /**
+ * Get the value of the Nth header.
+ *
+ * @param i the header index.
+ *
+ * @return the header value.
+ *
+ * @see #getHeaderName
+ */
+ public String getHeaderValue(int i)
+ {
+ if (i >= headers.size() || i < 0)
+ return null;
+
+ return ((HeaderElement)headers.get(i)).value;
}
}
Index: gnu/java/net/protocol/http/Request.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/net/protocol/http/Request.java,v
retrieving revision 1.8
diff -u -r1.8 Request.java
--- gnu/java/net/protocol/http/Request.java 9 Feb 2006 09:23:38 -0000 1.8
+++ gnu/java/net/protocol/http/Request.java 2 Mar 2006 21:18:29 -0000
@@ -302,12 +302,10 @@
String line = method + ' ' + requestUri + ' ' + version + CRLF;
out.write(line.getBytes(US_ASCII));
// Request headers
- for (Iterator i = requestHeaders.keySet().iterator();
- i.hasNext(); )
+ for (Iterator i = requestHeaders.iterator(); i.hasNext(); )
{
- String name =(String) i.next();
- String value =(String) requestHeaders.get(name);
- line = name + HEADER_SEP + value + CRLF;
+ Headers.HeaderElement elt = (Headers.HeaderElement)i.next();
+ line = elt.name + HEADER_SEP + elt.value + CRLF;
out.write(line.getBytes(US_ASCII));
}
out.write(CRLF.getBytes(US_ASCII));
@@ -438,23 +436,17 @@
void notifyHeaderHandlers(Headers headers)
{
- for (Iterator i = headers.entrySet().iterator(); i.hasNext(); )
+ for (Iterator i = headers.iterator(); i.hasNext(); )
{
- Map.Entry entry = (Map.Entry) i.next();
- String name =(String) entry.getKey();
+ Headers.HeaderElement entry = (Headers.HeaderElement) i.next();
// Handle Set-Cookie
- if ("Set-Cookie".equalsIgnoreCase(name))
- {
- String value = (String) entry.getValue();
- handleSetCookie(value);
- }
+ if ("Set-Cookie".equalsIgnoreCase(entry.name))
+ handleSetCookie(entry.value);
+
ResponseHeaderHandler handler =
- (ResponseHeaderHandler) responseHeaderHandlers.get(name);
+ (ResponseHeaderHandler) responseHeaderHandlers.get(entry.name);
if (handler != null)
- {
- String value = (String) entry.getValue();
- handler.setValue(value);
- }
+ handler.setValue(entry.value);
}
}
Index: gnu/java/net/protocol/http/HTTPURLConnection.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/net/protocol/http/HTTPURLConnection.java,v
retrieving revision 1.22
diff -u -r1.22 HTTPURLConnection.java
--- gnu/java/net/protocol/http/HTTPURLConnection.java 27 Feb 2006 23:37:20 -0000 1.22
+++ gnu/java/net/protocol/http/HTTPURLConnection.java 2 Mar 2006 21:18:29 -0000
@@ -422,20 +422,7 @@
if (connected)
throw new IllegalStateException("Already connected");
- HashMap m = new HashMap(requestHeaders);
- Iterator it = m.entrySet().iterator();
- while (it.hasNext())
- {
- Map.Entry e = (Map.Entry)it.next();
- String s = (String)e.getValue();
- String sa[] = s.split(",");
- ArrayList l = new ArrayList(sa.length);
- for (int i = 0; i < sa.length; i++)
- {
- l.add(sa[i].trim());
- }
- e.setValue(Collections.unmodifiableList(l));
- }
+ Map m = requestHeaders.getAsMap();
return Collections.unmodifiableMap(m);
}
@@ -449,16 +436,7 @@
public void addRequestProperty(String key, String value)
{
super.addRequestProperty(key, value);
-
- String old = requestHeaders.getValue(key);
- if (old == null)
- {
- requestHeaders.put(key, value);
- }
- else
- {
- requestHeaders.put(key, old + "," + value);
- }
+ requestHeaders.addValue(key, value);
}
public OutputStream getOutputStream()
@@ -533,17 +511,9 @@
return null;
}
}
- Headers headers = response.getHeaders();
- LinkedHashMap ret = new LinkedHashMap();
- ret.put(null, Collections.singletonList(getStatusLine(response)));
- for (Iterator i = headers.entrySet().iterator(); i.hasNext(); )
- {
- Map.Entry entry = (Map.Entry) i.next();
- String key = (String) entry.getKey();
- String value = (String) entry.getValue();
- ret.put(key, Collections.singletonList(value));
- }
- return Collections.unmodifiableMap(ret);
+ Map m = response.getHeaders().getAsMap();
+ m.put(null, Collections.singletonList(getStatusLine(response)));
+ return Collections.unmodifiableMap(m);
}
String getStatusLine(Response response)
@@ -571,20 +541,7 @@
{
return getStatusLine(response);
}
- Iterator i = response.getHeaders().entrySet().iterator();
- Map.Entry entry;
- int count = 1;
- do
- {
- if (!i.hasNext())
- {
- return null;
- }
- entry = (Map.Entry) i.next();
- count++;
- }
- while (count <= index);
- return (String) entry.getValue();
+ return response.getHeaders().getHeaderValue(index - 1);
}
public String getHeaderFieldKey(int index)
@@ -600,24 +557,8 @@
return null;
}
}
- if (index == 0)
- {
- return null;
- }
- Iterator i = response.getHeaders().entrySet().iterator();
- Map.Entry entry;
- int count = 1;
- do
- {
- if (!i.hasNext())
- {
- return null;
- }
- entry = (Map.Entry) i.next();
- count++;
- }
- while (count <= index);
- return (String) entry.getKey();
+ // index of zero is the status line.
+ return response.getHeaders().getHeaderName(index - 1);
}
public String getHeaderField(String name)
@@ -633,7 +574,7 @@
return null;
}
}
- return (String) response.getHeader(name);
+ return response.getHeader(name);
}
public long getHeaderFieldDate(String name, long def)