Repository: netty/netty
Issue: Add TextHeaders.getAndRemove(...)
Labels: feature

At the moment there is not way to get and remove a header with one call. This means you need to search the headers two times. We should add getAndRemove(...) to allow doing so with one call.


Issue comments:
Do we also need something like getIntAndRemove(...) ?

@plucury thanks for all the work. This can be closed now. 


Pull request review comments:
maybe start with 2 ? Seems more likely then 4.

why is this even needed ?

Again I think we should use 2

Same as above

I don't think an exception is the right thing here. I would even say that we should better change the return type to Long everywhere and return null in the case that it is not found. Sounds more sane to me.

@trustin wdyt ? 

I found that it is 4 in method `DefaultTextHeaders.getAll`. If we change the number here maybe should change that too.

It seems that we always add new header before the origin head. If we want return the list ordered by its append time, we need reverse it. 

@normanmaurer Yes, I agree with you. Using Long or Integer as return type seems more approach in this situation.

Sounds good to me. Let me handle it in another commit.

Will take care in another commit.


Patch that resolved the issue:
diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java
index 9d36a957a01..a6d3504d777 100644
--- a/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java
+++ b/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java
@@ -459,6 +459,158 @@ public long getTimeMillis(CharSequence name, long defaultValue) {
         return HttpHeaderDateFormat.get().parse(v.toString(), defaultValue);
     }
 
+    @Override
+    public CharSequence getUnconvertedAndRemove(CharSequence name) {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+        int h = hashCode(name);
+        int i = index(h);
+        HeaderEntry e = entries[i];
+        if (e == null) {
+            return null;
+        }
+
+        CharSequence value = null;
+        for (;;) {
+            if (e.hash == h && nameEquals(e.name, name)) {
+                value = e.value;
+                e.remove();
+                HeaderEntry next = e.next;
+                if (next != null) {
+                    entries[i] = next;
+                    e = next;
+                } else {
+                    entries[i] = null;
+                    return value;
+                }
+            } else {
+                break;
+            }
+        }
+
+        for (;;) {
+            HeaderEntry next = e.next;
+            if (next == null) {
+                break;
+            }
+            if (next.hash == h && nameEquals(next.name, name)) {
+                value = next.value;
+                e.next = next.next;
+                next.remove();
+            } else {
+                e = next;
+            }
+        }
+
+        if (value != null) {
+            return value;
+        }
+        return null;
+    }
+
+    @Override
+    public String getAndRemove(CharSequence name) {
+        CharSequence v = getUnconvertedAndRemove(name);
+        if (v == null) {
+            return null;
+        }
+        return v.toString();
+    }
+
+    @Override
+    public String getAndRemove(CharSequence name, String defaultValue) {
+        CharSequence v = getUnconvertedAndRemove(name);
+        if (v == null) {
+            return defaultValue;
+        }
+        return v.toString();
+    }
+
+    @Override
+    public int getIntAndRemove(CharSequence name) {
+        CharSequence v = getUnconvertedAndRemove(name);
+        if (v == null) {
+            throw new NoSuchElementException(String.valueOf(name));
+        }
+
+        if (v instanceof AsciiString) {
+            return ((AsciiString) v).parseInt();
+        } else {
+            return Integer.parseInt(v.toString());
+        }
+    }
+
+    @Override
+    public int getIntAndRemove(CharSequence name, int defaultValue) {
+        CharSequence v = getUnconvertedAndRemove(name);
+        if (v == null) {
+            return defaultValue;
+        }
+
+        try {
+            if (v instanceof AsciiString) {
+                return ((AsciiString) v).parseInt();
+            } else {
+                return Integer.parseInt(v.toString());
+            }
+        } catch (NumberFormatException ignored) {
+            return defaultValue;
+        }
+    }
+
+    @Override
+    public long getLongAndRemove(CharSequence name) {
+        CharSequence v = getUnconvertedAndRemove(name);
+        if (v == null) {
+            throw new NoSuchElementException(String.valueOf(name));
+        }
+
+        if (v instanceof AsciiString) {
+            return ((AsciiString) v).parseLong();
+        } else {
+            return Long.parseLong(v.toString());
+        }
+    }
+
+    @Override
+    public long getLongAndRemove(CharSequence name, long defaultValue) {
+        CharSequence v = getUnconvertedAndRemove(name);
+        if (v == null) {
+            return defaultValue;
+        }
+
+        try {
+            if (v instanceof AsciiString) {
+                return ((AsciiString) v).parseLong();
+            } else {
+                return Long.parseLong(v.toString());
+            }
+        } catch (NumberFormatException ignored) {
+            return defaultValue;
+        }
+    }
+
+    @Override
+    public long getTimeMillisAndRemove(CharSequence name) {
+        CharSequence v = getUnconvertedAndRemove(name);
+        if (v == null) {
+            throw new NoSuchElementException(String.valueOf(name));
+        }
+
+        return HttpHeaderDateFormat.get().parse(v.toString());
+    }
+
+    @Override
+    public long getTimeMillisAndRemove(CharSequence name, long defaultValue) {
+        CharSequence v = getUnconvertedAndRemove(name);
+        if (v == null) {
+            return defaultValue;
+        }
+
+        return HttpHeaderDateFormat.get().parse(v.toString(), defaultValue);
+    }
+
     @Override
     public List<CharSequence> getAllUnconverted(CharSequence name) {
         if (name == null) {
@@ -501,6 +653,104 @@ public List<String> getAll(CharSequence name) {
         return values;
     }
 
+    @Override
+    public List<String> getAllAndRemove(CharSequence name) {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+        int h = hashCode(name);
+        int i = index(h);
+        HeaderEntry e = entries[i];
+        if (e == null) {
+            return null;
+        }
+
+        List<String> values = new ArrayList<String>(4);
+        for (;;) {
+            if (e.hash == h && nameEquals(e.name, name)) {
+                values.add(e.getValue().toString());
+                e.remove();
+                HeaderEntry next = e.next;
+                if (next != null) {
+                    entries[i] = next;
+                    e = next;
+                } else {
+                    entries[i] = null;
+                    Collections.reverse(values);
+                    return values;
+                }
+            } else {
+                break;
+            }
+        }
+
+        for (;;) {
+            HeaderEntry next = e.next;
+            if (next == null) {
+                break;
+            }
+            if (next.hash == h && nameEquals(next.name, name)) {
+                values.add(next.getValue().toString());
+                e.next = next.next;
+                next.remove();
+            } else {
+                e = next;
+            }
+        }
+
+        Collections.reverse(values);
+        return values;
+    }
+
+    @Override
+    public List<CharSequence> getAllUnconvertedAndRemove(CharSequence name) {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+        int h = hashCode(name);
+        int i = index(h);
+        HeaderEntry e = entries[i];
+        if (e == null) {
+            return null;
+        }
+
+        List<CharSequence> values = new ArrayList<CharSequence>(4);
+        for (;;) {
+            if (e.hash == h && nameEquals(e.name, name)) {
+                values.add(e.getValue());
+                e.remove();
+                HeaderEntry next = e.next;
+                if (next != null) {
+                    entries[i] = next;
+                    e = next;
+                } else {
+                    entries[i] = null;
+                    Collections.reverse(values);
+                    return values;
+                }
+            } else {
+                break;
+            }
+        }
+
+        for (;;) {
+            HeaderEntry next = e.next;
+            if (next == null) {
+                break;
+            }
+            if (next.hash == h && nameEquals(next.name, name)) {
+                values.add(next.getValue());
+                e.next = next.next;
+                next.remove();
+            } else {
+                e = next;
+            }
+        }
+
+        Collections.reverse(values);
+        return values;
+    }
+
     @Override
     public List<Map.Entry<String, String>> entries() {
         int cnt = 0;
diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java
index e2e5ebf8fd1..b0249083d03 100644
--- a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java
+++ b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java
@@ -67,11 +67,56 @@ public long getTimeMillis(CharSequence name, long defaultValue) {
         return defaultValue;
     }
 
+    @Override
+    public String getAndRemove(CharSequence name) {
+        return null;
+    }
+
+    @Override
+    public String getAndRemove(CharSequence name, String defaultValue) {
+        return defaultValue;
+    }
+
+    @Override
+    public int getIntAndRemove(CharSequence name) {
+        throw new NoSuchElementException(String.valueOf(name));
+    }
+
+    @Override
+    public int getIntAndRemove(CharSequence name, int defaultValue) {
+        return defaultValue;
+    }
+
+    @Override
+    public long getLongAndRemove(CharSequence name) {
+        throw new NoSuchElementException(String.valueOf(name));
+    }
+
+    @Override
+    public long getLongAndRemove(CharSequence name, long defaultValue) {
+        return defaultValue;
+    }
+
+    @Override
+    public long getTimeMillisAndRemove(CharSequence name) {
+        throw new NoSuchElementException(String.valueOf(name));
+    }
+
+    @Override
+    public long getTimeMillisAndRemove(CharSequence name, long defaultValue) {
+        return defaultValue;
+    }
+
     @Override
     public CharSequence getUnconverted(CharSequence name) {
         return null;
     }
 
+    @Override
+    public CharSequence getUnconvertedAndRemove(CharSequence name) {
+        return null;
+    }
+
     @Override
     public List<String> getAll(CharSequence name) {
         return Collections.emptyList();
@@ -82,6 +127,16 @@ public List<CharSequence> getAllUnconverted(CharSequence name) {
         return Collections.emptyList();
     }
 
+    @Override
+    public List<String> getAllAndRemove(CharSequence name) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<CharSequence> getAllUnconvertedAndRemove(CharSequence name) {
+        return Collections.emptyList();
+    }
+
     @Override
     public List<Entry<String, String>> entries() {
         return Collections.emptyList();
diff --git a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java
index d84b2501820..97d9740f5e8 100644
--- a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java
+++ b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java
@@ -48,6 +48,94 @@ public interface TextHeaders extends Iterable<Map.Entry<String, String>> {
     long getTimeMillis(CharSequence name);
     long getTimeMillis(CharSequence name, long defaultValue);
 
+    /**
+     * Returns and Removes the value of a header with the specified name.  If there are
+     * more than one values for the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @return The first header value or {@code null} if there is no such header
+     */
+    String getAndRemove(CharSequence name);
+
+    /**
+     * Returns and Removes the value of a header with the specified name.  If there are
+     * more than one values for the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @param defaultValue default value
+     * @return The first header value or {@code defaultValue} if there is no such header
+     */
+    String getAndRemove(CharSequence name, String defaultValue);
+
+    /**
+     * Returns and Removes the integer value of a header with the specified name.  If there are
+     * more than one values for the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @return The first header value
+     * @throws java.util.NoSuchElementException
+     *         if no such header
+     * @throws NumberFormatException
+     *         if the value of header is not number
+     */
+    int getIntAndRemove(CharSequence name);
+
+    /**
+     * Returns and Removes the integer value of a header with the specified name.  If there are more than one values for
+     * the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @param defaultValue default value
+     * @return The first header value or {@code defaultValue} if there is no such header or the value of header is not
+     * number
+     */
+    int getIntAndRemove(CharSequence name, int defaultValue);
+
+    /**
+     * Returns and Removes the long value of a header with the specified name.  If there are
+     * more than one values for the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @return The first header value
+     * @throws java.util.NoSuchElementException
+     *         if no such header
+     * @throws NumberFormatException
+     *         if the value of header is not number
+     */
+    long getLongAndRemove(CharSequence name);
+
+    /**
+     * Returns and Removes the long value of a header with the specified name.  If there are more than one values for
+     * the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @param defaultValue default value
+     * @return The first header value or {@code defaultValue} if there is no such header or the value of header is not
+     * number
+     */
+    long getLongAndRemove(CharSequence name, long defaultValue);
+
+    /**
+     * Returns and Removes the millisecond value of a header with the specified name.  If there are
+     * more than one values for the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @return The first header value
+     * @throws java.util.NoSuchElementException
+     *         if no such header
+     */
+    long getTimeMillisAndRemove(CharSequence name);
+
+    /**
+     * Returns and Removes the millisecond value of a header with the specified name.  If there are more than one values
+     * for the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @param defaultValue default value
+     * @return The first header value or {@code defaultValue} if there is no such header
+     */
+    long getTimeMillisAndRemove(CharSequence name, long defaultValue);
+
     /**
      * Returns the value of a header with the specified name.  If there are
      * more than one values for the specified name, the first value is returned.
@@ -57,6 +145,15 @@ public interface TextHeaders extends Iterable<Map.Entry<String, String>> {
      */
     CharSequence getUnconverted(CharSequence name);
 
+    /**
+     * Returns and Removes the value of a header with the specified name.  If there are
+     * more than one values for the specified name, the first value is returned.
+     *
+     * @param name The name of the header to search
+     * @return The first header value or {@code null} if there is no such header
+     */
+    CharSequence getUnconvertedAndRemove(CharSequence name);
+
     /**
      * Returns the values of headers with the specified name
      *
@@ -73,6 +170,22 @@ public interface TextHeaders extends Iterable<Map.Entry<String, String>> {
      */
     List<CharSequence> getAllUnconverted(CharSequence name);
 
+    /**
+     * Returns and Removes the values of headers with the specified name
+     *
+     * @param name The name of the headers to search
+     * @return A {@link List} of header values which will be empty if no values are found
+     */
+    List<String> getAllAndRemove(CharSequence name);
+
+    /**
+     * Returns and Removes the values of headers with the specified name
+     *
+     * @param name The name of the headers to search
+     * @return A {@link List} of header values which will be empty if no values are found
+     */
+    List<CharSequence> getAllUnconvertedAndRemove(CharSequence name);
+
     /**
      * Returns a new {@link List} that contains all headers in this object.  Note that modifying the
      * returned {@link List} will not affect the state of this object.  If you intend to enumerate over the header


Test patch: