Method to check if a Http2 header is present and has a given value

Motivation:

With HTTP1, it's very easy to check if a header is present and has a
given value: you can simply invoke
io.netty.handler.codec.http.HttpHeaders#contains(java.lang.CharSequence, java.lang.CharSequence, boolean)

It is not possible to do the same with HTTP2. You have to get the list
of all headers (returned as String) and then iterate over it invoking
String#equals or String#equalsIgnoreCase

Modifications:

I've added io.netty.handler.codec.http2.Http2Headers#contains and
implemented it in DefaultHttp2Headers, EmptyHttp2Headers and ReadOnlyHttp2Headers.

Result:

You can use AsciiString constants to check if a header is present in a
consice and efficient manner.
This commit is contained in:
Thomas Segismont 2018-01-26 17:33:49 +01:00 committed by Scott Mitchell
parent 36304e1f05
commit bed74d8380
6 changed files with 69 additions and 20 deletions

View File

@ -14,10 +14,6 @@
*/
package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
import static io.netty.util.AsciiString.isUpperCase;
import io.netty.handler.codec.CharSequenceValueConverter;
import io.netty.handler.codec.DefaultHeaders;
import io.netty.util.AsciiString;
@ -25,6 +21,10 @@ import io.netty.util.ByteProcessor;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.UnstableApi;
import static io.netty.handler.codec.http2.Http2Error.*;
import static io.netty.handler.codec.http2.Http2Exception.*;
import static io.netty.util.AsciiString.*;
@UnstableApi
public class DefaultHttp2Headers
extends DefaultHeaders<CharSequence, CharSequence, Http2Headers> implements Http2Headers {
@ -183,6 +183,11 @@ public class DefaultHttp2Headers
return get(PseudoHeaderName.STATUS.value());
}
@Override
public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
return contains(name, value, caseInsensitive? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
}
@Override
protected final HeaderEntry<CharSequence, CharSequence> newHeaderEntry(int h, CharSequence name, CharSequence value,
HeaderEntry<CharSequence, CharSequence> next) {

View File

@ -75,4 +75,9 @@ public final class EmptyHttp2Headers
public CharSequence status() {
return get(PseudoHeaderName.STATUS.value());
}
@Override
public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
return false;
}
}

View File

@ -146,4 +146,16 @@ public interface Http2Headers extends Headers<CharSequence, CharSequence, Http2H
* Gets the {@link PseudoHeaderName#STATUS} header or {@code null} if there is no such header
*/
CharSequence status();
/**
* Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise.
* <p>
* If {@code caseInsensitive} is {@code true} then a case insensitive compare is done on the value.
*
* @param name the name of the header to find
* @param value the value of the header to find
* @param caseInsensitive {@code true} then a case insensitive compare is run to compare values.
* otherwise a case sensitive compare is run to compare values.
*/
boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive);
}

View File

@ -17,6 +17,7 @@ package io.netty.handler.codec.http2;
import io.netty.handler.codec.Headers;
import io.netty.util.AsciiString;
import io.netty.util.HashingStrategy;
import java.util.ArrayList;
import java.util.Collections;
@ -27,9 +28,10 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import static io.netty.handler.codec.CharSequenceValueConverter.INSTANCE;
import static io.netty.handler.codec.http2.DefaultHttp2Headers.HTTP2_NAME_VALIDATOR;
import static io.netty.util.internal.EmptyArrays.EMPTY_ASCII_STRINGS;
import static io.netty.handler.codec.CharSequenceValueConverter.*;
import static io.netty.handler.codec.http2.DefaultHttp2Headers.*;
import static io.netty.util.AsciiString.*;
import static io.netty.util.internal.EmptyArrays.*;
/**
* A variant of {@link Http2Headers} which only supports read-only methods.
@ -769,6 +771,30 @@ public final class ReadOnlyHttp2Headers implements Http2Headers {
return get(PseudoHeaderName.STATUS.value());
}
@Override
public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
final int nameHash = AsciiString.hashCode(name);
final HashingStrategy<CharSequence> strategy = caseInsensitive? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER;
final int valueHash = strategy.hashCode(value);
return contains(name, nameHash, value, valueHash, strategy, pseudoHeaders)
|| contains(name, nameHash, value, valueHash, strategy, otherHeaders);
}
private static boolean contains(CharSequence name, int nameHash, CharSequence value, int valueHash,
HashingStrategy<CharSequence> hashingStrategy, AsciiString[] headers) {
final int headersEnd = headers.length - 1;
for (int i = 0; i < headersEnd; i += 2) {
AsciiString roName = headers[i];
AsciiString roValue = headers[i + 1];
if (roName.hashCode() == nameHash && roValue.hashCode() == valueHash &&
roName.contentEqualsIgnoreCase(name) && hashingStrategy.equals(roValue, value)) {
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');

View File

@ -22,12 +22,8 @@ import org.junit.Test;
import java.util.Map.Entry;
import static io.netty.util.AsciiString.of;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static io.netty.util.AsciiString.*;
import static org.junit.Assert.*;
public class DefaultHttp2HeadersTest {
@ -147,6 +143,13 @@ public class DefaultHttp2HeadersTest {
assertEquals(1, http2Headers.names().size());
}
@Test
public void testContainsNameAndValue() {
Http2Headers headers = newHeaders();
assertFalse(headers.contains("name1", "Value2", false));
assertTrue(headers.contains("name1", "Value2", true));
}
private static void verifyAllPseudoHeadersPresent(Http2Headers headers) {
for (PseudoHeaderName pseudoName : PseudoHeaderName.values()) {
assertNotNull(headers.get(pseudoName.value()));

View File

@ -22,12 +22,8 @@ import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import static io.netty.handler.codec.http2.DefaultHttp2HeadersTest.verifyPseudoHeadersFirst;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static io.netty.handler.codec.http2.DefaultHttp2HeadersTest.*;
import static org.junit.Assert.*;
public class ReadOnlyHttp2HeadersTest {
@Test(expected = IllegalArgumentException.class)
@ -139,7 +135,9 @@ public class ReadOnlyHttp2HeadersTest {
@Test
public void testContainsNameAndValue() {
Http2Headers headers = newClientHeaders();
assertTrue(headers.contains("Name1", "Value1"));
assertTrue(headers.contains("Name1", "value1"));
assertFalse(headers.contains("Name1", "Value1", false));
assertTrue(headers.contains("Name1", "Value1", true));
assertTrue(headers.contains(Http2Headers.PseudoHeaderName.PATH.value(), "/foo"));
assertFalse(headers.contains(Http2Headers.PseudoHeaderName.STATUS.value(), "200"));
assertFalse(headers.contains("a missing header", "a missing value"));