Removing HTTP2 module's dependency on guava

Motivation:

The use of the guava library library is fairly superficial and can
easily be removed to reduce the overall dependency list of the module.
Cleaning up the HTTP2 code to use common Netty utilities, etc.

Modifications:

- Various changes to remove use of guava preconditions and collections.
- Changed default charset to use CharsetUtil.UTF_8.
- Changed StreamState to use ArrayDeque instead of LinkedList.
- Changed precondition checks to throw NPE
- Changed implementation of Http2Headers to more closely mirror the SPDY
implementation.

Result:

The behavior of the HTTP2 module will remain unchanged.
This commit is contained in:
nmittler 2014-04-05 13:06:16 -07:00 committed by Norman Maurer
parent cd579f75d2
commit 98bfc8e600
21 changed files with 869 additions and 218 deletions

View File

@ -45,11 +45,6 @@
<artifactId>hpack</artifactId>
<version>0.6.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0.1</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>

View File

@ -0,0 +1,513 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.netty.handler.codec.http2.draft10;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
/**
* An immutable collection of headers sent or received via HTTP/2.
*/
public final class DefaultHttp2Headers extends Http2Headers {
private static final int MAX_VALUE_LENGTH = 0xFFFF; // Length is a 16-bit field
private static final int BUCKET_SIZE = 17;
private final HeaderEntry[] entries;
private final HeaderEntry head;
private DefaultHttp2Headers(Builder builder) {
this.entries = builder.entries;
this.head = builder.head;
}
@Override
public String get(String name) {
if (name == null) {
throw new NullPointerException("name");
}
int h = hash(name);
int i = index(h);
HeaderEntry e = entries[i];
while (e != null) {
if (e.hash == h && eq(name, e.key)) {
return e.value;
}
e = e.next;
}
return null;
}
@Override
public List<String> getAll(String name) {
if (name == null) {
throw new NullPointerException("name");
}
LinkedList<String> values = new LinkedList<String>();
int h = hash(name);
int i = index(h);
HeaderEntry e = entries[i];
while (e != null) {
if (e.hash == h && eq(name, e.key)) {
values.addFirst(e.value);
}
e = e.next;
}
return values;
}
@Override
public List<Entry<String, String>> entries() {
List<Map.Entry<String, String>> all =
new LinkedList<Map.Entry<String, String>>();
HeaderEntry e = head.after;
while (e != head) {
all.add(e);
e = e.after;
}
return all;
}
@Override
public boolean contains(String name) {
return get(name) != null;
}
@Override
public boolean isEmpty() {
return head == head.after;
}
@Override
public Set<String> names() {
Set<String> names = new TreeSet<String>();
HeaderEntry e = head.after;
while (e != head) {
names.add(e.key);
e = e.after;
}
return names;
}
@Override
public Iterator<Entry<String, String>> iterator() {
return new HeaderIterator();
}
/**
* Short cut for {@code new DefaultHttp2Headers.Builder()}.
*/
public static Builder newBuilder() {
return new Builder();
}
/**
* Builds instances of {@link DefaultHttp2Header}.
*/
public static class Builder {
private HeaderEntry[] entries;
private HeaderEntry head;
private Http2Headers buildResults;
public Builder() {
clear();
}
public void set(Http2Headers headers) {
// No need to lazy copy the previous results, since we're starting from scratch.
clear();
for (Map.Entry<String, String> entry : headers) {
add(entry.getKey(), entry.getValue());
}
}
public Builder add(final String name, final Object value) {
// If this is the first call on the builder since the last build, copy the previous
// results.
lazyCopy();
String lowerCaseName = name.toLowerCase();
validateHeaderName(lowerCaseName);
String strVal = toString(value);
validateHeaderValue(strVal);
int nameHash = hash(lowerCaseName);
int hashTableIndex = index(nameHash);
add0(nameHash, hashTableIndex, lowerCaseName, strVal);
return this;
}
public Builder remove(final String name) {
if (name == null) {
throw new NullPointerException("name");
}
// If this is the first call on the builder since the last build, copy the previous
// results.
lazyCopy();
String lowerCaseName = name.toLowerCase();
int nameHash = hash(lowerCaseName);
int hashTableIndex = index(nameHash);
remove0(nameHash, hashTableIndex, lowerCaseName);
return this;
}
public Builder set(final String name, final Object value) {
// If this is the first call on the builder since the last build, copy the previous
// results.
lazyCopy();
String lowerCaseName = name.toLowerCase();
validateHeaderName(lowerCaseName);
String strVal = toString(value);
validateHeaderValue(strVal);
int nameHash = hash(lowerCaseName);
int hashTableIndex = index(nameHash);
remove0(nameHash, hashTableIndex, lowerCaseName);
add0(nameHash, hashTableIndex, lowerCaseName, strVal);
return this;
}
public Builder set(final String name, final Iterable<?> values) {
if (values == null) {
throw new NullPointerException("values");
}
// If this is the first call on the builder since the last build, copy the previous
// results.
lazyCopy();
String lowerCaseName = name.toLowerCase();
validateHeaderName(lowerCaseName);
int nameHash = hash(lowerCaseName);
int hashTableIndex = index(nameHash);
remove0(nameHash, hashTableIndex, lowerCaseName);
for (Object v: values) {
if (v == null) {
break;
}
String strVal = toString(v);
validateHeaderValue(strVal);
add0(nameHash, hashTableIndex, lowerCaseName, strVal);
}
return this;
}
public Builder clear() {
// No lazy copy required, since we're just creating a new array.
entries = new HeaderEntry[BUCKET_SIZE];
head = new HeaderEntry(-1, null, null);
head.before = head.after = head;
buildResults = null;
return this;
}
/**
* Sets the {@link HttpName#METHOD} header.
*/
public Builder setMethod(String method) {
return set(HttpName.METHOD.value(), method);
}
/**
* Sets the {@link HttpName#SCHEME} header.
*/
public Builder setScheme(String scheme) {
return set(HttpName.SCHEME.value(), scheme);
}
/**
* Sets the {@link HttpName#AUTHORITY} header.
*/
public Builder setAuthority(String authority) {
return set(HttpName.AUTHORITY.value(), authority);
}
/**
* Sets the {@link HttpName#PATH} header.
*/
public Builder setPath(String path) {
return set(HttpName.PATH.value(), path);
}
/**
* Sets the {@link HttpName#STATUS} header.
*/
public Builder setStatus(String status) {
return set(HttpName.STATUS.value(), status);
}
/**
* Builds a new instance of {@link DefaultHttp2Headers}.
*/
public DefaultHttp2Headers build() {
// If this is the first call on the builder since the last build, copy the previous
// results.
lazyCopy();
// Give the multimap over to the headers instance and save the build results for
// future lazy copies if this builder is used again later.
DefaultHttp2Headers headers = new DefaultHttp2Headers(this);
buildResults = headers;
return headers;
}
/**
* Performs a lazy copy of the last build results, if there are any. For the typical use
* case, headers will only be built once so no copy will be required. If the any method
* is called on the builder after that, it will force a copy of the most recently created
* headers object.
*/
private void lazyCopy() {
if (buildResults != null) {
set(buildResults);
buildResults = null;
}
}
private void add0(int hash, int hashTableIndex, final String name, final String value) {
// Update the hash table.
HeaderEntry e = entries[hashTableIndex];
HeaderEntry newEntry;
entries[hashTableIndex] = newEntry = new HeaderEntry(hash, name, value);
newEntry.next = e;
// Update the linked list.
newEntry.addBefore(head);
}
private void remove0(int hash, int hashTableIndex, String name) {
HeaderEntry e = entries[hashTableIndex];
if (e == null) {
return;
}
for (;;) {
if (e.hash == hash && eq(name, e.key)) {
e.remove();
HeaderEntry next = e.next;
if (next != null) {
entries[hashTableIndex] = next;
e = next;
} else {
entries[hashTableIndex] = null;
return;
}
} else {
break;
}
}
for (;;) {
HeaderEntry next = e.next;
if (next == null) {
break;
}
if (next.hash == hash && eq(name, next.key)) {
e.next = next.next;
next.remove();
} else {
e = next;
}
}
}
private static String toString(Object value) {
if (value == null) {
return null;
}
return value.toString();
}
}
private static int hash(String name) {
int h = 0;
for (int i = name.length() - 1; i >= 0; i --) {
char c = name.charAt(i);
if (c >= 'A' && c <= 'Z') {
c += 32;
}
h = 31 * h + c;
}
if (h > 0) {
return h;
} else if (h == Integer.MIN_VALUE) {
return Integer.MAX_VALUE;
} else {
return -h;
}
}
private static boolean eq(String name1, String name2) {
int nameLen = name1.length();
if (nameLen != name2.length()) {
return false;
}
for (int i = nameLen - 1; i >= 0; i --) {
char c1 = name1.charAt(i);
char c2 = name2.charAt(i);
if (c1 != c2) {
if (c1 >= 'A' && c1 <= 'Z') {
c1 += 32;
}
if (c2 >= 'A' && c2 <= 'Z') {
c2 += 32;
}
if (c1 != c2) {
return false;
}
}
}
return true;
}
private static int index(int hash) {
return hash % BUCKET_SIZE;
}
/**
* Validate a HTTP2 header name.
*/
private static void validateHeaderName(String name) {
if (name == null) {
throw new NullPointerException("name");
}
if (name.isEmpty()) {
throw new IllegalArgumentException(
"name cannot be length zero");
}
// Since name may only contain ascii characters, for valid names
// name.length() returns the number of bytes when UTF-8 encoded.
if (name.length() > MAX_VALUE_LENGTH) {
throw new IllegalArgumentException(
"name exceeds allowable length: " + name);
}
for (int i = 0; i < name.length(); i ++) {
char c = name.charAt(i);
if (c == 0) {
throw new IllegalArgumentException(
"name contains null character: " + name);
}
if (c > 127) {
throw new IllegalArgumentException(
"name contains non-ascii character: " + name);
}
}
}
/**
* Validate a HTTP2 header value. Does not validate max length.
*/
private static void validateHeaderValue(String value) {
if (value == null) {
throw new NullPointerException("value");
}
for (int i = 0; i < value.length(); i ++) {
char c = value.charAt(i);
if (c == 0) {
throw new IllegalArgumentException(
"value contains null character: " + value);
}
}
}
private final class HeaderIterator implements Iterator<Map.Entry<String, String>> {
private HeaderEntry current = head;
@Override
public boolean hasNext() {
return current.after != head;
}
@Override
public Entry<String, String> next() {
current = current.after;
if (current == head) {
throw new NoSuchElementException();
}
return current;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private static final class HeaderEntry implements Map.Entry<String, String> {
final int hash;
final String key;
final String value;
HeaderEntry next;
HeaderEntry before, after;
HeaderEntry(int hash, String key, String value) {
this.hash = hash;
this.key = key;
this.value = value;
}
void remove() {
before.after = after;
after.before = before;
}
void addBefore(HeaderEntry e) {
after = e;
before = e.before;
before.after = this;
after.before = this;
}
@Override
public String getKey() {
return key;
}
@Override
public String getValue() {
return value;
}
@Override
public String setValue(String value) {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return key + '=' + value;
}
}
}

View File

@ -15,14 +15,57 @@
package io.netty.handler.codec.http2.draft10;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.Set;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMultimap;
/**
* An immutable collection of headers sent or received via HTTP/2.
*/
public abstract class Http2Headers implements Iterable<Entry<String, String>> {
public final class Http2Headers implements Iterable<Entry<String, String>> {
public static final Http2Headers EMPTY_HEADERS = new Http2Headers() {
@Override
public String get(String name) {
return null;
}
@Override
public List<String> getAll(String name) {
return Collections.emptyList();
}
@Override
public List<Entry<String, String>> entries() {
return Collections.emptyList();
}
@Override
public boolean contains(String name) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public Set<String> names() {
return Collections.emptySet();
}
@Override
public Iterator<Entry<String, String>> iterator() {
return entries().iterator();
}
};
/**
* HTTP2 header names.
@ -64,130 +107,136 @@ public final class Http2Headers implements Iterable<Entry<String, String>> {
}
}
private final ImmutableMultimap<String, String> headers;
/**
* Returns the {@link Set} of all header names.
*/
public abstract Set<String> names();
private Http2Headers(Builder builder) {
this.headers = builder.map.build();
/**
* Returns the header value with the specified header name. If there is
* more than one header value for the specified header name, the first
* value is returned.
*
* @return the header value or {@code null} if there is no such header
*/
public abstract String get(String name);
/**
* Returns the header values with the specified header name.
*
* @return the {@link List} of header values. An empty list if there is no
* such header.
*/
public abstract List<String> getAll(String name);
/**
* Returns all header names and values that this frame contains.
*
* @return the {@link List} of the header name-value pairs. An empty list
* if there is no header in this message.
*/
public abstract List<Map.Entry<String, String>> entries();
/**
* Returns {@code true} if and only if there is a header with the specified
* header name.
*/
public abstract boolean contains(String name);
/**
* Checks if no header exists.
*/
public abstract boolean isEmpty();
/**
* Gets the {@link HttpName#METHOD} header.
*
* @return the header value or {@code null} if there is no such header
*/
public final String getMethod() {
return get(HttpName.METHOD.value());
}
public String getHeader(String name) {
ImmutableCollection<String> col = getHeaders(name);
return col.isEmpty() ? null : col.iterator().next();
/**
* Gets the {@link HttpName#SCHEME} header.
*
* @return the header value or {@code null} if there is no such header
*/
public final String getScheme() {
return get(HttpName.SCHEME.value());
}
public ImmutableCollection<String> getHeaders(String name) {
return headers.get(name);
/**
* Gets the {@link HttpName#AUTHORITY} header.
*
* @return the header value or {@code null} if there is no such header
*/
public final String getAuthority() {
return get(HttpName.AUTHORITY.value());
}
public String getMethod() {
return getHeader(HttpName.METHOD.value());
/**
* Gets the {@link HttpName#PATH} header.
*
* @return the header value or {@code null} if there is no such header
*/
public final String getPath() {
return get(HttpName.PATH.value());
}
public String getScheme() {
return getHeader(HttpName.SCHEME.value());
}
public String getAuthority() {
return getHeader(HttpName.AUTHORITY.value());
}
public String getPath() {
return getHeader(HttpName.PATH.value());
}
public String getStatus() {
return getHeader(HttpName.STATUS.value());
}
@Override
public Iterator<Entry<String, String>> iterator() {
return headers.entries().iterator();
/**
* Gets the {@link HttpName#STATUS} header.
*
* @return the header value or {@code null} if there is no such header
*/
public final String getStatus() {
return get(HttpName.STATUS.value());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((headers == null) ? 0 : headers.hashCode());
for (String name : names()) {
result = prime * result + name.hashCode();
Set<String> values = new TreeSet<String>(getAll(name));
for (String value : values) {
result = prime * result + value.hashCode();
}
}
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
public boolean equals(Object o) {
if (!(o instanceof Http2Headers)) {
return false;
}
if (getClass() != obj.getClass()) {
Http2Headers other = (Http2Headers) o;
// First, check that the set of names match.
Set<String> names = names();
if (!names.equals(other.names())) {
return false;
}
Http2Headers other = (Http2Headers) obj;
if (headers == null) {
if (other.headers != null) {
// Compare the values for each name.
for (String name : names) {
List<String> values = getAll(name);
List<String> otherValues = other.getAll(name);
if (values.size() != otherValues.size()) {
return false;
}
// Convert the values to a set and remove values from the other object to see if
// they match.
Set<String> valueSet = new HashSet<String>(values);
valueSet.removeAll(otherValues);
if (!valueSet.isEmpty()) {
return false;
}
} else if (!headers.equals(other.headers)) {
return false;
}
// They match.
return true;
}
@Override
public String toString() {
return headers.toString();
}
public static class Builder {
private ImmutableMultimap.Builder<String, String> map = ImmutableMultimap.builder();
public Builder clear() {
map = ImmutableMultimap.builder();
return this;
}
public Builder addHeaders(Http2Headers headers) {
if (headers == null) {
throw new IllegalArgumentException("headers must not be null.");
}
map.putAll(headers.headers);
return this;
}
public Builder addHeader(String name, String value) {
// Use interning on the header name to save space.
map.put(name.intern(), value);
return this;
}
public Builder addHeader(byte[] name, byte[] value) {
addHeader(new String(name, Charsets.UTF_8), new String(value, Charsets.UTF_8));
return this;
}
public Builder setMethod(String value) {
return addHeader(HttpName.METHOD.value(), value);
}
public Builder setScheme(String value) {
return addHeader(HttpName.SCHEME.value(), value);
}
public Builder setAuthority(String value) {
return addHeader(HttpName.AUTHORITY.value(), value);
}
public Builder setPath(String value) {
return addHeader(HttpName.PATH.value(), value);
}
public Builder setStatus(String value) {
return addHeader(HttpName.STATUS.value(), value);
}
public Http2Headers build() {
return new Http2Headers(this);
}
}
}

View File

@ -20,7 +20,6 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.toByteBuf;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
@ -32,23 +31,37 @@ import io.netty.handler.codec.http2.draft10.connection.Http2Stream.State;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2GoAwayFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2GoAwayFrame;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.TreeMultiset;
import java.util.Set;
import java.util.TreeSet;
public class DefaultHttp2Connection implements Http2Connection {
/**
* Used to sort streams in the activeStreams set. Sort by priority first, then by stream ID.
* Streams with the same ID are considered equal.
*/
private static final Comparator<Http2Stream> STREAM_COMPARATOR = new Comparator<Http2Stream>() {
@Override
public int compare(Http2Stream s1, Http2Stream s2) {
int p1 = s1.getPriority();
int p2 = s2.getPriority();
private final List<Listener> listeners = Lists.newArrayList();
private final Map<Integer, Http2Stream> streamMap = Maps.newHashMap();
private final Multiset<Http2Stream> activeStreams = TreeMultiset.create();
// Sort streams with the same priority by their ID.
if (p1 == p2) {
return s1.getId() - s2.getId();
}
return p1 - p2;
}
};
private final List<Listener> listeners = new ArrayList<Listener>();
private final Map<Integer, Http2Stream> streamMap = new HashMap<Integer, Http2Stream>();
private final Set<Http2Stream> activeStreams = new TreeSet<Http2Stream>(STREAM_COMPARATOR);
private final DefaultEndpoint localEndpoint;
private final DefaultEndpoint remoteEndpoint;
private boolean goAwaySent;
@ -85,10 +98,10 @@ public class DefaultHttp2Connection implements Http2Connection {
}
@Override
public List<Http2Stream> getActiveStreams() {
public Set<Http2Stream> getActiveStreams() {
// Copy the list in case any operation on the returned streams causes the activeStreams set
// to change.
return ImmutableList.copyOf(activeStreams);
return Collections.unmodifiableSet(activeStreams);
}
@Override
@ -181,21 +194,14 @@ public class DefaultHttp2Connection implements Http2Connection {
return state;
}
@Override
public int compareTo(Http2Stream other) {
// Sort streams with the same priority by their ID.
if (priority == other.getPriority()) {
return id - other.getId();
}
return priority - other.getPriority();
}
@Override
public void verifyState(Http2Error error, State... allowedStates) throws Http2Exception {
Predicate<State> predicate = Predicates.in(Arrays.asList(allowedStates));
if (!predicate.apply(state)) {
throw format(error, "Stream %d in unexpected state: %s", id, state);
for (State allowedState : allowedStates) {
if (state == allowedState) {
return;
}
}
throw format(error, "Stream %d in unexpected state: %s", id, state);
}
@Override

View File

@ -19,16 +19,14 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.flowControlErr
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.CONNECTION_STREAM_ID;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2WindowUpdateFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.Maps;
/**
* Basic implementation of {@link InboundFlowController}.
*/
@ -36,7 +34,7 @@ public class DefaultInboundFlowController implements InboundFlowController {
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
private final StreamWindow connectionWindow = new StreamWindow(CONNECTION_STREAM_ID);
private final Map<Integer, StreamWindow> streamWindows = Maps.newHashMap();
private final Map<Integer, StreamWindow> streamWindows = new HashMap<Integer, StreamWindow>();
public DefaultInboundFlowController(Http2Connection connection) {
connection.addListener(new Http2Connection.Listener() {

View File

@ -21,25 +21,23 @@ import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.CONNECTION_STREAM_ID;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2StreamException;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* Basic implementation of {@link OutboundFlowController}.
*/
public class DefaultOutboundFlowController implements OutboundFlowController {
private final Http2Connection connection;
private final Map<Integer, StreamState> streamStates = Maps.newHashMap();
private final Map<Integer, StreamState> streamStates = new HashMap<Integer, StreamState>();
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
private int connectionWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
@ -185,7 +183,7 @@ public class DefaultOutboundFlowController implements OutboundFlowController {
*/
private class StreamState {
private final int streamId;
private final Queue<PendingWrite> pendingWriteQueue = Lists.newLinkedList();
private final Queue<PendingWrite> pendingWriteQueue = new ArrayDeque<PendingWrite>();
private int windowSize = initialWindowSize;
public StreamState(int streamId) {

View File

@ -19,7 +19,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import java.util.List;
import java.util.Collection;
public interface Http2Connection {
@ -132,7 +132,7 @@ public interface Http2Connection {
* Gets all streams that are currently either open or half-closed. The returned collection is
* sorted by priority.
*/
List<Http2Stream> getActiveStreams();
Collection<Http2Stream> getActiveStreams();
/**
* Gets a view of this connection from the local {@link Endpoint}.

View File

@ -23,7 +23,7 @@ import io.netty.handler.codec.http2.draft10.Http2Exception;
/**
* A single stream within an HTTP2 connection. Streams are compared to each other by priority.
*/
public interface Http2Stream extends Comparable<Http2Stream> {
public interface Http2Stream {
/**
* The allowed states of an HTTP2 stream.

View File

@ -16,7 +16,7 @@
package io.netty.handler.codec.http2.draft10.frame;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
import io.netty.handler.codec.http2.draft10.DefaultHttp2Headers;
import io.netty.handler.codec.http2.draft10.Http2Headers;
public final class DefaultHttp2HeadersFrame implements Http2HeadersFrame {
@ -104,7 +104,7 @@ public final class DefaultHttp2HeadersFrame implements Http2HeadersFrame {
public static class Builder {
private int streamId;
private int priority = DEFAULT_STREAM_PRIORITY;
private final Http2Headers.Builder headersBuilder = new Http2Headers.Builder();
private final DefaultHttp2Headers.Builder headersBuilder = new DefaultHttp2Headers.Builder();
private boolean endOfStream;
public Builder setStreamId(int streamId) {
@ -128,12 +128,12 @@ public final class DefaultHttp2HeadersFrame implements Http2HeadersFrame {
return this;
}
public Http2Headers.Builder headers() {
public DefaultHttp2Headers.Builder headers() {
return headersBuilder;
}
public Builder setHeaders(Http2Headers headers) {
headersBuilder.addHeaders(headers);
headersBuilder.set(headers);
return this;
}

View File

@ -17,12 +17,13 @@ package io.netty.handler.codec.http2.draft10.frame.decoder;
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_MAX_HEADER_SIZE;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.handler.codec.http2.draft10.DefaultHttp2Headers;
import io.netty.handler.codec.http2.draft10.Http2Error;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2Headers;
import io.netty.util.CharsetUtil;
import java.io.IOException;
@ -45,11 +46,12 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder {
@Override
public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
try {
final Http2Headers.Builder headersBuilder = new Http2Headers.Builder();
final DefaultHttp2Headers.Builder headersBuilder = new DefaultHttp2Headers.Builder();
HeaderListener listener = new HeaderListener() {
@Override
public void emitHeader(byte[] key, byte[] value) {
headersBuilder.addHeader(key, value);
headersBuilder.add(new String(key, CharsetUtil.UTF_8), new String(value,
CharsetUtil.UTF_8));
}
};

View File

@ -31,8 +31,6 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
import io.netty.handler.codec.http2.draft10.frame.Http2HeadersFrame;
import com.google.common.base.Preconditions;
/**
* An unmarshaller for {@link Http2HeadersFrame} instances.
*/
@ -41,7 +39,10 @@ public class Http2HeadersFrameUnmarshaller extends AbstractHeadersUnmarshaller {
private final Http2HeadersDecoder headersDecoder;
public Http2HeadersFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
this.headersDecoder = Preconditions.checkNotNull(headersDecoder, "headersDecoder");
if (headersDecoder == null) {
throw new IllegalArgumentException("headersDecoder must not be null.");
}
this.headersDecoder = headersDecoder;
}
@Override

View File

@ -30,8 +30,6 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
import io.netty.handler.codec.http2.draft10.frame.Http2PushPromiseFrame;
import com.google.common.base.Preconditions;
/**
* An unmarshaller for {@link Http2PushPromiseFrame} instances.
*/
@ -40,7 +38,10 @@ public class Http2PushPromiseFrameUnmarshaller extends AbstractHeadersUnmarshall
private final Http2HeadersDecoder headersDecoder;
public Http2PushPromiseFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
this.headersDecoder = Preconditions.checkNotNull(headersDecoder, "headersDecoder");
if (headersDecoder == null) {
throw new IllegalArgumentException("headersDecoder must not be null.");
}
this.headersDecoder = headersDecoder;
}
@Override

View File

@ -16,7 +16,7 @@
package io.netty.handler.codec.http2.draft10.frame.encoder;
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.util.CharsetUtil.UTF_8;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.handler.codec.http2.draft10.Http2Error;
@ -25,14 +25,11 @@ import io.netty.handler.codec.http2.draft10.Http2Headers;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Map.Entry;
import com.google.common.base.Charsets;
import com.twitter.hpack.Encoder;
public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder {
private static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
private final Encoder encoder;
@ -45,8 +42,8 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder {
try {
OutputStream stream = new ByteBufOutputStream(buffer);
for (Entry<String, String> header : headers) {
byte[] key = header.getKey().getBytes(DEFAULT_CHARSET);
byte[] value = header.getValue().getBytes(DEFAULT_CHARSET);
byte[] key = header.getKey().getBytes(UTF_8);
byte[] value = header.getValue().getBytes(UTF_8);
encoder.encodeHeader(stream, key, value);
}
encoder.endHeaders(stream);

View File

@ -29,15 +29,16 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2HeadersFrame;
import com.google.common.base.Preconditions;
public class Http2HeadersFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2HeadersFrame> {
private final Http2HeadersEncoder headersEncoder;
public Http2HeadersFrameMarshaller(Http2HeadersEncoder headersEncoder) {
super(Http2HeadersFrame.class);
this.headersEncoder = Preconditions.checkNotNull(headersEncoder, "headersEncoder");
if (headersEncoder == null) {
throw new NullPointerException("headersEncoder must not be null.");
}
this.headersEncoder = headersEncoder;
}
@Override

View File

@ -26,8 +26,6 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2PushPromiseFrame;
import com.google.common.base.Preconditions;
public class Http2PushPromiseFrameMarshaller extends
AbstractHttp2FrameMarshaller<Http2PushPromiseFrame> {
@ -35,7 +33,10 @@ public class Http2PushPromiseFrameMarshaller extends
public Http2PushPromiseFrameMarshaller(Http2HeadersEncoder headersEncoder) {
super(Http2PushPromiseFrame.class);
this.headersEncoder = Preconditions.checkNotNull(headersEncoder, "headersEncoder");
if (headersEncoder == null) {
throw new NullPointerException("headersEncoder must not be null.");
}
this.headersEncoder = headersEncoder;
}
@Override

View File

@ -0,0 +1,83 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http2.draft10;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.junit.Test;
/**
* Tests for {@link DefaultHttp2Headers}.
*/
public class DefaultHttp2HeadersTest {
@Test
public void duplicateKeysShouldStoreAllValues() {
DefaultHttp2Headers headers =
DefaultHttp2Headers.newBuilder().add("a", "1").add("a", "2")
.add("a", "3").build();
List<String> aValues = headers.getAll("a");
assertEquals(3, aValues.size());
assertEquals("1", aValues.get(0));
assertEquals("2", aValues.get(1));
assertEquals("3", aValues.get(2));
}
@Test(expected = NoSuchElementException.class)
public void iterateEmptyHeadersShouldThrow() {
Iterator<Map.Entry<String, String>> iterator =
DefaultHttp2Headers.newBuilder().build().iterator();
assertFalse(iterator.hasNext());
iterator.next();
}
@Test
public void iterateHeadersShouldReturnAllValues() {
Set<String> headers = new HashSet<String>();
headers.add("a:1");
headers.add("a:2");
headers.add("a:3");
headers.add("b:1");
headers.add("b:2");
headers.add("c:1");
// Build the headers from the input set.
DefaultHttp2Headers.Builder builder = DefaultHttp2Headers.newBuilder();
for (String header : headers) {
String[] parts = header.split(":");
builder.add(parts[0], parts[1]);
}
// Now iterate through the headers, removing them from the original set.
for (Map.Entry<String, String> entry : builder.build()) {
assertTrue(headers.remove(entry.getKey() + ":" + entry.getValue()));
}
// Make sure we removed them all.
assertTrue(headers.isEmpty());
}
}

View File

@ -32,6 +32,7 @@ import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.connection.Http2Connection.Listener;
import io.netty.handler.codec.http2.draft10.connection.Http2Stream.State;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
@ -225,7 +226,7 @@ public class DefaultHttp2ConnectionTest {
server.local().createStream(4, 256, true);
server.remote().createStream(3, Integer.MAX_VALUE, true);
server.remote().createStream(5, 1, false);
List<Http2Stream> activeStreams = server.getActiveStreams();
List<Http2Stream> activeStreams = activeStreams();
assertEquals(2, activeStreams.get(0).getId());
assertEquals(5, activeStreams.get(1).getId());
assertEquals(4, activeStreams.get(2).getId());
@ -244,7 +245,7 @@ public class DefaultHttp2ConnectionTest {
// Make this this highest priority.
stream7.setPriority(0);
List<Http2Stream> activeStreams = server.getActiveStreams();
List<Http2Stream> activeStreams = activeStreams();
assertEquals(7, activeStreams.get(0).getId());
assertEquals(2, activeStreams.get(1).getId());
assertEquals(5, activeStreams.get(2).getId());
@ -305,4 +306,8 @@ public class DefaultHttp2ConnectionTest {
stream.close(ctx, future);
verify(ctx).close(promise);
}
private List<Http2Stream> activeStreams() {
return new ArrayList<Http2Stream>(server.getActiveStreams());
}
}

View File

@ -23,6 +23,9 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http2.draft10.Http2Exception;
@ -39,8 +42,6 @@ import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.google.common.collect.ImmutableList;
/**
* Tests for {@link DefaultOutboundFlowController}.
*/
@ -74,7 +75,7 @@ public class DefaultOutboundFlowControllerTest {
return null;
}
}).when(connection).addListener(any(Listener.class));
when(connection.getActiveStreams()).thenReturn(ImmutableList.of(stream));
when(connection.getActiveStreams()).thenReturn(Arrays.asList(stream));
when(stream.getId()).thenReturn(STREAM_ID);
controller = new DefaultOutboundFlowController(connection);

View File

@ -17,6 +17,7 @@ package io.netty.handler.codec.http2.draft10.connection;
import static io.netty.handler.codec.http2.draft10.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
import static io.netty.util.CharsetUtil.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
@ -51,15 +52,13 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2PingFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2SettingsFrame;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import com.google.common.collect.ImmutableList;
/**
* Tests for {@link Http2ConnectionHandler}.
*/
@ -111,7 +110,7 @@ public class Http2ConnectionHandlerTest {
when(channel.isActive()).thenReturn(true);
when(stream.getId()).thenReturn(STREAM_ID);
when(pushStream.getId()).thenReturn(PUSH_STREAM_ID);
when(connection.getActiveStreams()).thenReturn(ImmutableList.of(stream));
when(connection.getActiveStreams()).thenReturn(Arrays.asList(stream));
when(connection.getStream(STREAM_ID)).thenReturn(stream);
when(connection.getStreamOrFail(STREAM_ID)).thenReturn(stream);
when(connection.local()).thenReturn(local);
@ -157,7 +156,7 @@ public class Http2ConnectionHandlerTest {
@Test
public void inboundDataAfterGoAwayShouldApplyFlowControl() throws Exception {
when(connection.isGoAwaySent()).thenReturn(true);
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
ByteBuf data = Unpooled.copiedBuffer("Hello", UTF_8);
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(data).build();
handler.channelRead(ctx, frame);
@ -168,7 +167,7 @@ public class Http2ConnectionHandlerTest {
@Test
public void inboundDataWithEndOfStreamShouldCloseRemoteSide() throws Exception {
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
ByteBuf data = Unpooled.copiedBuffer("Hello", UTF_8);
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setEndOfStream(true)
.setContent(data).build();
@ -182,7 +181,7 @@ public class Http2ConnectionHandlerTest {
@Test
public void inboundDataShouldSucceed() throws Exception {
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
ByteBuf data = Unpooled.copiedBuffer("Hello", UTF_8);
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(data).build();
handler.channelRead(ctx, frame);
@ -198,7 +197,7 @@ public class Http2ConnectionHandlerTest {
when(connection.isGoAwaySent()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.channelRead(ctx, frame);
verify(remote, never()).createStream(eq(STREAM_ID), eq(1), eq(false));
verify(ctx, never()).fireChannelRead(frame);
@ -209,7 +208,7 @@ public class Http2ConnectionHandlerTest {
int newStreamId = 5;
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(newStreamId).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.channelRead(ctx, frame);
verify(remote).createStream(eq(newStreamId), eq(1), eq(false));
verify(ctx).fireChannelRead(frame);
@ -220,7 +219,7 @@ public class Http2ConnectionHandlerTest {
int newStreamId = 5;
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(newStreamId).setPriority(1)
.setEndOfStream(true).setHeaders(new Http2Headers.Builder().build())
.setEndOfStream(true).setHeaders(Http2Headers.EMPTY_HEADERS)
.build();
handler.channelRead(ctx, frame);
verify(remote).createStream(eq(newStreamId), eq(1), eq(true));
@ -231,7 +230,7 @@ public class Http2ConnectionHandlerTest {
public void inboundHeadersWithForPromisedStreamShouldHalfOpenStream() throws Exception {
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.channelRead(ctx, frame);
verify(stream).openForPush();
verify(ctx).fireChannelRead(frame);
@ -241,7 +240,7 @@ public class Http2ConnectionHandlerTest {
public void inboundHeadersWithForPromisedStreamShouldCloseStream() throws Exception {
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setEndOfStream(true).setHeaders(new Http2Headers.Builder().build())
.setEndOfStream(true).setHeaders(Http2Headers.EMPTY_HEADERS)
.build();
handler.channelRead(ctx, frame);
verify(stream).openForPush();
@ -255,7 +254,7 @@ public class Http2ConnectionHandlerTest {
Http2Frame frame =
new DefaultHttp2PushPromiseFrame.Builder().setStreamId(STREAM_ID)
.setPromisedStreamId(PUSH_STREAM_ID)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.channelRead(ctx, frame);
verify(remote, never()).reservePushStream(eq(PUSH_STREAM_ID), eq(stream));
verify(ctx, never()).fireChannelRead(frame);
@ -266,7 +265,7 @@ public class Http2ConnectionHandlerTest {
Http2Frame frame =
new DefaultHttp2PushPromiseFrame.Builder().setStreamId(STREAM_ID)
.setPromisedStreamId(PUSH_STREAM_ID)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.channelRead(ctx, frame);
verify(remote).reservePushStream(eq(PUSH_STREAM_ID), eq(stream));
verify(ctx).fireChannelRead(frame);
@ -419,7 +418,7 @@ public class Http2ConnectionHandlerTest {
@Test
public void outboundDataAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
ByteBuf data = Unpooled.copiedBuffer("Hello", UTF_8);
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(data).build();
handler.write(ctx, frame, promise);
@ -431,7 +430,7 @@ public class Http2ConnectionHandlerTest {
@Test
public void outboundDataShouldApplyFlowControl() throws Exception {
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
ByteBuf data = Unpooled.copiedBuffer("Hello", UTF_8);
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(data).build();
handler.write(ctx, frame, promise);
@ -447,7 +446,7 @@ public class Http2ConnectionHandlerTest {
when(connection.isGoAway()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(ctx, never()).writeAndFlush(frame, promise);
@ -458,7 +457,7 @@ public class Http2ConnectionHandlerTest {
int newStreamId = 5;
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(newStreamId).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.write(ctx, frame, promise);
verify(local).createStream(eq(newStreamId), eq(1), eq(false));
verify(promise, never()).setFailure(any(Http2Exception.class));
@ -470,7 +469,7 @@ public class Http2ConnectionHandlerTest {
int newStreamId = 5;
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(newStreamId).setPriority(1)
.setEndOfStream(true).setHeaders(new Http2Headers.Builder().build())
.setEndOfStream(true).setHeaders(Http2Headers.EMPTY_HEADERS)
.build();
handler.write(ctx, frame, promise);
verify(local).createStream(eq(newStreamId), eq(1), eq(true));
@ -482,7 +481,7 @@ public class Http2ConnectionHandlerTest {
public void outboundHeadersShouldOpenStreamForPush() throws Exception {
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.write(ctx, frame, promise);
verify(stream).openForPush();
verify(stream, never()).closeLocalSide(eq(ctx), eq(future));
@ -494,7 +493,7 @@ public class Http2ConnectionHandlerTest {
public void outboundHeadersShouldClosePushStream() throws Exception {
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setEndOfStream(true).setHeaders(new Http2Headers.Builder().build())
.setEndOfStream(true).setHeaders(Http2Headers.EMPTY_HEADERS)
.build();
handler.write(ctx, frame, promise);
verify(stream).openForPush();
@ -509,7 +508,7 @@ public class Http2ConnectionHandlerTest {
Http2Frame frame =
new DefaultHttp2PushPromiseFrame.Builder().setStreamId(STREAM_ID)
.setPromisedStreamId(PUSH_STREAM_ID)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(local, never()).reservePushStream(eq(PUSH_STREAM_ID), eq(stream));
@ -521,7 +520,7 @@ public class Http2ConnectionHandlerTest {
Http2Frame frame =
new DefaultHttp2PushPromiseFrame.Builder().setStreamId(STREAM_ID)
.setPromisedStreamId(PUSH_STREAM_ID)
.setHeaders(new Http2Headers.Builder().build()).build();
.setHeaders(Http2Headers.EMPTY_HEADERS).build();
handler.write(ctx, frame, promise);
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(local).reservePushStream(eq(PUSH_STREAM_ID), eq(stream));

View File

@ -15,6 +15,7 @@
package io.netty.handler.codec.http2.draft10.frame;
import static io.netty.util.CharsetUtil.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@ -33,6 +34,7 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http2.draft10.DefaultHttp2Headers;
import io.netty.handler.codec.http2.draft10.Http2Headers;
import io.netty.util.NetUtil;
import io.netty.util.ReferenceCountUtil;
@ -44,12 +46,10 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import com.google.common.base.Charsets;
/**
* Tests encoding/decoding each HTTP2 frame type.
*/
public class Http2FrameRoundtripTest {
public class DefaultHttp2FrameRoundtripTest {
private static final EventLoopGroup group = new NioEventLoopGroup();
@ -121,7 +121,7 @@ public class Http2FrameRoundtripTest {
@Test
public void headersFrameWithoutPriorityShouldMatch() throws Exception {
Http2Headers headers =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
new DefaultHttp2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2").build();
Http2HeadersFrame in =
new DefaultHttp2HeadersFrame.Builder().setHeaders(headers).setEndOfStream(true)
@ -134,7 +134,7 @@ public class Http2FrameRoundtripTest {
@Test
public void headersFrameWithPriorityShouldMatch() throws Exception {
Http2Headers headers =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
new DefaultHttp2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2").build();
Http2HeadersFrame in =
new DefaultHttp2HeadersFrame.Builder().setHeaders(headers).setEndOfStream(true)
@ -158,7 +158,7 @@ public class Http2FrameRoundtripTest {
@Test
public void pingFrameShouldMatch() throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("01234567", Charsets.UTF_8);
ByteBuf buf = Unpooled.copiedBuffer("01234567", UTF_8);
Http2PingFrame in =
new DefaultHttp2PingFrame.Builder().setAck(true).setData(buf).build().retain();
@ -180,7 +180,7 @@ public class Http2FrameRoundtripTest {
@Test
public void pushPromiseFrameShouldMatch() throws Exception {
Http2Headers headers =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
new DefaultHttp2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2").build();
Http2PushPromiseFrame in =
new DefaultHttp2PushPromiseFrame.Builder().setHeaders(headers)
@ -224,7 +224,7 @@ public class Http2FrameRoundtripTest {
@Test
public void stressTest() throws Exception {
Http2Headers headers =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
new DefaultHttp2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2").build();
String text = "hello world";
int numStreams = 1000;

View File

@ -19,6 +19,7 @@ package io.netty.handler.codec.http2.draft10.frame;
import static org.junit.Assert.assertEquals;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http2.draft10.DefaultHttp2Headers;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2Headers;
import io.netty.handler.codec.http2.draft10.frame.decoder.DefaultHttp2HeadersDecoder;
@ -52,33 +53,33 @@ public class HeaderBlockRoundtripTest {
@Test
public void roundtripShouldBeSuccessful() throws Http2Exception {
Http2Headers in =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
new DefaultHttp2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2")
.addHeader("accept", "image/png").addHeader("cache-control", "no-cache")
.addHeader("custom", "value1").addHeader("custom", "value2")
.addHeader("custom", "value3").addHeader("custom", "custom4").build();
.add("accept", "image/png").add("cache-control", "no-cache")
.add("custom", "value1").add("custom", "value2")
.add("custom", "value3").add("custom", "custom4").build();
assertRoundtripSuccessful(in);
}
@Test
public void successiveCallsShouldSucceed() throws Http2Exception {
Http2Headers in =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
new DefaultHttp2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path")
.addHeader("accept", "*/*").build();
.add("accept", "*/*").build();
assertRoundtripSuccessful(in);
in =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
new DefaultHttp2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource1")
.addHeader("accept", "image/jpeg").addHeader("cache-control", "no-cache")
.add("accept", "image/jpeg").add("cache-control", "no-cache")
.build();
assertRoundtripSuccessful(in);
in =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
new DefaultHttp2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2")
.addHeader("accept", "image/png").addHeader("cache-control", "no-cache")
.add("accept", "image/png").add("cache-control", "no-cache")
.build();
assertRoundtripSuccessful(in);
}