First cut of frame encoding/decoding and session management for HTTP2

Motivation:

Needed a rough performance comparison between SPDY and HTTP 2.0 framing.
Expected performance gains were seen in HTTP 2.0 due to header
compression.

Modifications:

Added a new codec-http2 module containing all of the new source and unit
tests.  Updated the top-level pom.xml to add this as a child module.

Result:

Netty will have basic support for HTTP2.
This commit is contained in:
nmittler 2014-03-27 10:40:47 -07:00
parent 5d141242c9
commit 754e08796b
81 changed files with 8602 additions and 0 deletions

61
codec-http2/pom.xml Normal file
View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>5.0.0.Alpha2-SNAPSHOT</version>
</parent>
<artifactId>netty-codec-http2</artifactId>
<packaging>jar</packaging>
<name>Netty/Codec/HTTP2</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twitter</groupId>
<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>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,45 @@
/*
* 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;
/**
* All error codes identified by the HTTP2 spec.
*/
public enum Http2Error {
NO_ERROR(0),
PROTOCOL_ERROR(1),
INTERNAL_ERROR(2),
FLOW_CONTROL_ERROR(3),
SETTINGS_TIMEOUT(4),
STREAM_CLOSED(5),
FRAME_SIZE_ERROR(6),
REFUSED_STREAM(7),
CANCEL(8),
COMPRESSION_ERROR(9),
CONNECT_ERROR(10),
ENHANCE_YOUR_CALM(11),
INADEQUATE_SECURITY(12);
private final int code;
private Http2Error(int code) {
this.code = code;
}
public int getCode() {
return this.code;
}
}

View File

@ -0,0 +1,50 @@
/*
* 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;
/**
* Exception thrown when an HTTP2 error was encountered.
*/
public class Http2Exception extends Exception {
private static final long serialVersionUID = -2292608019080068769L;
private final Http2Error error;
public Http2Exception(Http2Error error) {
this.error = error;
}
public Http2Exception(Http2Error error, String message) {
super(message);
this.error = error;
}
public Http2Error getError() {
return error;
}
public static Http2Exception format(Http2Error error, String fmt, Object... args) {
return new Http2Exception(error, String.format(fmt, args));
}
public static Http2Exception protocolError(String fmt, Object... args) {
return format(Http2Error.PROTOCOL_ERROR, fmt, args);
}
public static Http2Exception flowControlError(String fmt, Object... args) {
return format(Http2Error.FLOW_CONTROL_ERROR, fmt, args);
}
}

View File

@ -0,0 +1,193 @@
/*
* 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.Map.Entry;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMultimap;
public final class Http2Headers implements Iterable<Entry<String, String>> {
/**
* HTTP2 header names.
*/
public enum HttpName {
/**
* {@code :method}.
*/
METHOD(":method"),
/**
* {@code :scheme}.
*/
SCHEME(":scheme"),
/**
* {@code :authority}.
*/
AUTHORITY(":authority"),
/**
* {@code :path}.
*/
PATH(":path"),
/**
* {@code :status}.
*/
STATUS(":status");
private final String value;
private HttpName(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}
private final ImmutableMultimap<String, String> headers;
private Http2Headers(Builder builder) {
this.headers = builder.map.build();
}
public String getHeader(String name) {
ImmutableCollection<String> col = getHeaders(name);
return col.isEmpty() ? null : col.iterator().next();
}
public ImmutableCollection<String> getHeaders(String name) {
return headers.get(name);
}
public String getMethod() {
return getHeader(HttpName.METHOD.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();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((headers == null) ? 0 : headers.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Http2Headers other = (Http2Headers) obj;
if (headers == null) {
if (other.headers != null) {
return false;
}
} else if (!headers.equals(other.headers)) {
return false;
}
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

@ -0,0 +1,36 @@
/*
* 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;
public class Http2StreamException extends Http2Exception {
private static final long serialVersionUID = -7658235659648480024L;
private final int streamId;
public Http2StreamException(int streamId, Http2Error error, String message) {
super(error, message);
this.streamId = streamId;
}
public Http2StreamException(int streamId, Http2Error error) {
super(error);
this.streamId = streamId;
}
public int getStreamId() {
return streamId;
}
}

View File

@ -0,0 +1,456 @@
/*
* 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.connection;
import static io.netty.handler.codec.http2.draft10.Http2Error.NO_ERROR;
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;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http2.draft10.Http2Error;
import io.netty.handler.codec.http2.draft10.Http2Exception;
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.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;
public class DefaultHttp2Connection implements Http2Connection {
private final List<Listener> listeners = Lists.newArrayList();
private final Map<Integer, Http2Stream> streamMap = Maps.newHashMap();
private final Multiset<Http2Stream> activeStreams = TreeMultiset.create();
private final DefaultEndpoint localEndpoint;
private final DefaultEndpoint remoteEndpoint;
private boolean goAwaySent;
private boolean goAwayReceived;
private ChannelFutureListener closeListener;
public DefaultHttp2Connection(boolean server) {
this.localEndpoint = new DefaultEndpoint(server);
this.remoteEndpoint = new DefaultEndpoint(!server);
}
@Override
public void addListener(Listener listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener listener) {
listeners.remove(listener);
}
@Override
public Http2Stream getStreamOrFail(int streamId) throws Http2Exception {
Http2Stream stream = getStream(streamId);
if (stream == null) {
throw protocolError("Stream does not exist %d", streamId);
}
return stream;
}
@Override
public Http2Stream getStream(int streamId) {
return streamMap.get(streamId);
}
@Override
public List<Http2Stream> getActiveStreams() {
// Copy the list in case any operation on the returned streams causes the activeStreams set
// to change.
return ImmutableList.copyOf(activeStreams);
}
@Override
public Endpoint local() {
return localEndpoint;
}
@Override
public Endpoint remote() {
return remoteEndpoint;
}
@Override
public void sendGoAway(ChannelHandlerContext ctx, ChannelPromise promise, Http2Exception cause) {
closeListener = getOrCreateCloseListener(ctx, promise);
ChannelFuture future;
if (!goAwaySent) {
goAwaySent = true;
int errorCode = cause != null ? cause.getError().getCode() : NO_ERROR.getCode();
ByteBuf debugData = toByteBuf(ctx, cause);
Http2GoAwayFrame frame = new DefaultHttp2GoAwayFrame.Builder().setErrorCode(errorCode)
.setLastStreamId(remote().getLastStreamCreated()).setDebugData(debugData).build();
future = ctx.writeAndFlush(frame);
} else {
future = ctx.newSucceededFuture();
}
// If there are no active streams, close immediately after the send is complete.
// Otherwise wait until all streams are inactive.
if (cause != null || activeStreams.isEmpty()) {
future.addListener(closeListener);
}
}
@Override
public void goAwayReceived() {
goAwayReceived = true;
}
@Override
public boolean isGoAwaySent() {
return goAwaySent;
}
@Override
public boolean isGoAwayReceived() {
return goAwayReceived;
}
@Override
public boolean isGoAway() {
return isGoAwaySent() || isGoAwayReceived();
}
private ChannelFutureListener getOrCreateCloseListener(final ChannelHandlerContext ctx,
final ChannelPromise promise) {
if (closeListener == null) {
closeListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
ctx.close(promise);
}
};
}
return closeListener;
}
private void notifyStreamClosed(int id) {
for (Listener listener : listeners) {
listener.streamClosed(id);
}
}
private void notifyStreamCreated(int id) {
for (Listener listener : listeners) {
listener.streamCreated(id);
}
}
/**
* Simple stream implementation. Streams can be compared to each other by priority.
*/
private class DefaultStream implements Http2Stream {
private final int id;
private State state = State.IDLE;
private int priority;
public DefaultStream(int id) {
this.id = id;
this.priority = DEFAULT_STREAM_PRIORITY;
}
@Override
public int getId() {
return id;
}
@Override
public State getState() {
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);
}
}
@Override
public void setPriority(int priority) throws Http2Exception {
if (priority < 0) {
throw protocolError("Invalid priority: %d", priority);
}
// If it was active, we must remove it from the set before changing the priority.
// Otherwise it won't be able to locate the stream in the set.
boolean wasActive = activeStreams.remove(this);
this.priority = priority;
// If this stream was in the active set, re-add it so that it's properly sorted.
if (wasActive) {
activeStreams.add(this);
}
}
@Override
public int getPriority() {
return priority;
}
@Override
public void openForPush() throws Http2Exception {
switch (state) {
case RESERVED_LOCAL:
state = State.HALF_CLOSED_REMOTE;
break;
case RESERVED_REMOTE:
state = State.HALF_CLOSED_LOCAL;
break;
default:
throw protocolError("Attempting to open non-reserved stream for push");
}
}
@Override
public void close(ChannelHandlerContext ctx, ChannelFuture future) {
if (state == State.CLOSED) {
return;
}
state = State.CLOSED;
activeStreams.remove(this);
streamMap.remove(id);
notifyStreamClosed(id);
// If this connection is closing and there are no longer any
// active streams, close after the current operation completes.
if (closeListener != null && activeStreams.isEmpty()) {
future.addListener(closeListener);
}
}
@Override
public void closeLocalSide(ChannelHandlerContext ctx, ChannelFuture future) {
switch (state) {
case OPEN:
case HALF_CLOSED_LOCAL:
state = State.HALF_CLOSED_LOCAL;
break;
case HALF_CLOSED_REMOTE:
case RESERVED_LOCAL:
case RESERVED_REMOTE:
case IDLE:
case CLOSED:
default:
close(ctx, future);
break;
}
}
@Override
public void closeRemoteSide(ChannelHandlerContext ctx, ChannelFuture future) {
switch (state) {
case OPEN:
case HALF_CLOSED_REMOTE:
state = State.HALF_CLOSED_REMOTE;
break;
case RESERVED_LOCAL:
case RESERVED_REMOTE:
case IDLE:
case HALF_CLOSED_LOCAL:
case CLOSED:
default:
close(ctx, future);
break;
}
}
@Override
public boolean isRemoteSideOpen() {
switch (state) {
case HALF_CLOSED_LOCAL:
case OPEN:
case RESERVED_REMOTE:
return true;
case IDLE:
case RESERVED_LOCAL:
case HALF_CLOSED_REMOTE:
case CLOSED:
default:
return false;
}
}
@Override
public boolean isLocalSideOpen() {
switch (state) {
case HALF_CLOSED_REMOTE:
case OPEN:
case RESERVED_LOCAL:
return true;
case IDLE:
case RESERVED_REMOTE:
case HALF_CLOSED_LOCAL:
case CLOSED:
default:
return false;
}
}
}
/**
* Simple endpoint implementation.
*/
private class DefaultEndpoint implements Endpoint {
private int nextStreamId;
private int lastStreamCreated;
private int maxStreams = Integer.MAX_VALUE;
private boolean pushToAllowed = true;
public DefaultEndpoint(boolean serverEndpoint) {
// Determine the starting stream ID for this endpoint. Zero is reserved for the
// connection and 1 is reserved for responding to an upgrade from HTTP 1.1.
// Client-initiated streams use odd identifiers and server-initiated streams use
// even.
nextStreamId = serverEndpoint ? 2 : 3;
}
@Override
public DefaultStream createStream(int streamId, int priority, boolean halfClosed)
throws Http2Exception {
checkNewStreamAllowed(streamId);
// Create and initialize the stream.
DefaultStream stream = new DefaultStream(streamId);
stream.setPriority(priority);
if (halfClosed) {
stream.state = isLocal() ? State.HALF_CLOSED_LOCAL : State.HALF_CLOSED_REMOTE;
} else {
stream.state = State.OPEN;
}
// Update the next and last stream IDs.
nextStreamId += 2;
lastStreamCreated = streamId;
// Register the stream and mark it as active.
streamMap.put(streamId, stream);
activeStreams.add(stream);
notifyStreamCreated(streamId);
return stream;
}
@Override
public DefaultStream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception {
if (parent == null) {
throw protocolError("Parent stream missing");
}
if (isLocal() ? !parent.isLocalSideOpen() : !parent.isRemoteSideOpen()) {
throw protocolError("Stream %d is not open for sending push promise", parent.getId());
}
if (!opposite().isPushToAllowed()) {
throw protocolError("Server push not allowed to opposite endpoint.");
}
// Create and initialize the stream.
DefaultStream stream = new DefaultStream(streamId);
stream.setPriority(parent.getPriority() + 1);
stream.state = isLocal() ? State.RESERVED_LOCAL : State.RESERVED_REMOTE;
// Update the next and last stream IDs.
nextStreamId += 2;
lastStreamCreated = streamId;
// Register the stream.
streamMap.put(streamId, stream);
notifyStreamCreated(streamId);
return stream;
}
@Override
public void setPushToAllowed(boolean allow) {
this.pushToAllowed = allow;
}
@Override
public boolean isPushToAllowed() {
return pushToAllowed;
}
@Override
public int getMaxStreams() {
return maxStreams;
}
@Override
public void setMaxStreams(int maxStreams) {
this.maxStreams = maxStreams;
}
@Override
public int getLastStreamCreated() {
return lastStreamCreated;
}
@Override
public Endpoint opposite() {
return isLocal() ? remoteEndpoint : localEndpoint;
}
private void checkNewStreamAllowed(int streamId) throws Http2Exception {
if (isGoAway()) {
throw protocolError("Cannot create a stream since the connection is going away");
}
if (nextStreamId < 0) {
throw protocolError("No more streams can be created on this connection");
}
if (streamId != nextStreamId) {
throw protocolError("Incorrect next stream ID requested: %d", streamId);
}
if (streamMap.size() + 1 > maxStreams) {
// TODO(nathanmittler): is this right?
throw protocolError("Maximum streams exceeded for this endpoint.");
}
}
private boolean isLocal() {
return this == localEndpoint;
}
}
}

View File

@ -0,0 +1,210 @@
/*
* 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.connection;
import static io.netty.handler.codec.http2.draft10.Http2Exception.flowControlError;
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.Map;
import com.google.common.collect.Maps;
/**
* Basic implementation of {@link InboundFlowController}.
*/
public class DefaultInboundFlowController implements InboundFlowController {
private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
private StreamWindow connectionWindow = new StreamWindow(CONNECTION_STREAM_ID);
private final Map<Integer, StreamWindow> streamWindows = Maps.newHashMap();
public DefaultInboundFlowController(Http2Connection connection) {
connection.addListener(new Http2Connection.Listener() {
@Override
public void streamCreated(int streamId) {
streamWindows.put(streamId, new StreamWindow(streamId));
}
@Override
public void streamClosed(int streamId) {
streamWindows.remove(streamId);
}
});
}
@Override
public void setInitialInboundWindowSize(int newWindowSize) throws Http2Exception {
int deltaWindowSize = newWindowSize - initialWindowSize;
initialWindowSize = newWindowSize;
// Apply the delta to all of the windows.
connectionWindow.addAndGet(deltaWindowSize);
for (StreamWindow window : streamWindows.values()) {
window.updatedInitialWindowSize(deltaWindowSize);
}
}
@Override
public void applyInboundFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
throws Http2Exception {
applyConnectionFlowControl(dataFrame, frameWriter);
applyStreamFlowControl(dataFrame, frameWriter);
}
/**
* Apply connection-wide flow control to the incoming data frame.
*/
private void applyConnectionFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
throws Http2Exception {
// Remove the data length from the available window size. Throw if the lower bound
// was exceeded.
connectionWindow.addAndGet(-dataFrame.content().readableBytes());
// If less than the window update threshold remains, restore the window size
// to the initial value and send a window update to the remote endpoint indicating
// the new window size.
if (connectionWindow.getSize() <= getWindowUpdateThreshold()) {
connectionWindow.updateWindow(frameWriter);
}
}
/**
* Apply stream-based flow control to the incoming data frame.
*/
private void applyStreamFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
throws Http2Exception {
// Remove the data length from the available window size. Throw if the lower bound
// was exceeded.
StreamWindow window = getWindowOrFail(dataFrame.getStreamId());
window.addAndGet(-dataFrame.content().readableBytes());
// If less than the window update threshold remains, restore the window size
// to the initial value and send a window update to the remote endpoint indicating
// the new window size.
if (window.getSize() <= getWindowUpdateThreshold() && !dataFrame.isEndOfStream()) {
window.updateWindow(frameWriter);
}
}
/**
* Gets the threshold for a window size below which a window update should be issued.
*/
private int getWindowUpdateThreshold() {
return initialWindowSize / 2;
}
/**
* Gets the window for the stream or raises a {@code PROTOCOL_ERROR} if not found.
*/
private StreamWindow getWindowOrFail(int streamId) throws Http2Exception {
StreamWindow window = streamWindows.get(streamId);
if (window == null) {
throw protocolError("Flow control window missing for stream: %d", streamId);
}
return window;
}
/**
* Flow control window state for an individual stream.
*/
private final class StreamWindow {
private int windowSize;
private int lowerBound;
private final int streamId;
public StreamWindow(int streamId) {
this.streamId = streamId;
this.windowSize = initialWindowSize;
}
public int getSize() {
return windowSize;
}
/**
* Adds the given delta to the window size and returns the new value.
*
* @param delta the delta in the initial window size.
* @throws Http2Exception thrown if the new window is less than the allowed lower bound.
*/
public int addAndGet(int delta) throws Http2Exception {
// Apply the delta. Even if we throw an exception we want to have taken this delta into
// account.
windowSize += delta;
if (delta > 0) {
lowerBound = 0;
}
// Window size can become negative if we sent a SETTINGS frame that reduces the
// size of the transfer window after the peer has written data frames.
// The value is bounded by the length that SETTINGS frame decrease the window.
// This difference is stored for the connection when writing the SETTINGS frame
// and is cleared once we send a WINDOW_UPDATE frame.
if (windowSize < lowerBound) {
if (streamId == CONNECTION_STREAM_ID) {
throw protocolError("Connection flow control window exceeded");
} else {
throw flowControlError("Flow control window exceeded for stream: %d", streamId);
}
}
return windowSize;
}
/**
* Called when sending a SETTINGS frame with a new initial window size. If the window has gotten
* smaller (i.e. deltaWindowSize < 0), the lower bound is set to that value. This will
* temporarily allow for receipt of data frames which were sent by the remote endpoint before
* receiving the SETTINGS frame.
*
* @param delta the delta in the initial window size.
* @throws Http2Exception thrown if integer overflow occurs on the window.
*/
public void updatedInitialWindowSize(int delta) throws Http2Exception {
windowSize += delta;
if (delta > 0 && windowSize < Integer.MIN_VALUE + delta) {
// Integer overflow.
throw flowControlError("Flow control window overflowed for stream: %d", streamId);
}
if (delta < 0) {
lowerBound = delta;
}
}
/**
* Called to perform a window update for this stream (or connection). Updates the window size
* back to the size of the initial window and sends a window update frame to the remote
* endpoint.
*/
public void updateWindow(FrameWriter frameWriter) throws Http2Exception {
// Expand the window for this stream back to the size of the initial window.
int deltaWindowSize = initialWindowSize - getSize();
addAndGet(deltaWindowSize);
// Send a window update for the stream/connection.
Http2WindowUpdateFrame updateFrame = new DefaultHttp2WindowUpdateFrame.Builder()
.setStreamId(streamId).setWindowSizeIncrement(deltaWindowSize).build();
frameWriter.writeFrame(updateFrame);
}
}
}

View File

@ -0,0 +1,291 @@
/*
* 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.connection;
import static io.netty.handler.codec.http2.draft10.Http2Error.FLOW_CONTROL_ERROR;
import static io.netty.handler.codec.http2.draft10.Http2Error.STREAM_CLOSED;
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.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 int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
private int connectionWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
public DefaultOutboundFlowController(Http2Connection connection) {
this.connection = connection;
connection.addListener(new Http2Connection.Listener() {
@Override
public void streamCreated(int streamId) {
streamStates.put(streamId, new StreamState(streamId));
}
@Override
public void streamClosed(int streamId) {
StreamState state = streamStates.remove(streamId);
if (state != null) {
state.clearPendingWrites();
}
}
});
}
@Override
public void setInitialOutboundWindowSize(int newWindowSize) throws Http2Exception {
int delta = newWindowSize - initialWindowSize;
initialWindowSize = newWindowSize;
addAndGetConnectionWindowSize(delta);
for (StreamState window : streamStates.values()) {
// Verify that the maximum value is not exceeded by this change.
window.addAndGetWindow(delta);
}
if (delta > 0) {
// The window size increased, send any pending frames for all streams.
writePendingFrames();
}
}
@Override
public void updateOutboundWindowSize(int streamId, int delta) throws Http2Exception {
StreamState streamWindow = null;
if (streamId == CONNECTION_STREAM_ID) {
// Update the connection window and write any pending frames for all streams.
addAndGetConnectionWindowSize(delta);
writePendingFrames();
} else {
// Update the stream window and write any pending frames for the stream.
streamWindow = getStateOrFail(streamId);
streamWindow.addAndGetWindow(delta);
streamWindow.writePendingFrames(Integer.MAX_VALUE);
}
}
@Override
public void sendFlowControlled(Http2DataFrame frame, FrameWriter frameWriter)
throws Http2Exception {
StreamState streamState = getStateOrFail(frame.getStreamId());
int dataLength = frame.content().readableBytes();
if (streamState.writableWindow() >= dataLength) {
// Window size is large enough to send entire data frame
writeFrame(frame, streamState, frameWriter);
return;
}
// Enqueue the frame to be written when the window size permits.
streamState.addPendingWrite(new PendingWrite(frame, frameWriter));
if (streamState.writableWindow() <= 0) {
// Stream is stalled, don't send anything now.
return;
}
// Create and send a partial frame up to the window size.
Http2DataFrame partialFrame = readPartialFrame(frame, streamState.writableWindow());
writeFrame(partialFrame, streamState, frameWriter);
}
/**
* Attempts to get the {@link StreamState} for the given stream. If not available, raises a
* {@code PROTOCOL_ERROR}.
*/
private StreamState getStateOrFail(int streamId) throws Http2Exception {
StreamState streamState = streamStates.get(streamId);
if (streamState == null) {
throw protocolError("Missing flow control window for stream: %d", streamId);
}
return streamState;
}
/**
* Writes the frame and decrements the stream and connection window sizes.
*/
private void writeFrame(Http2DataFrame frame, StreamState state, FrameWriter frameWriter)
throws Http2Exception {
int dataLength = frame.content().readableBytes();
connectionWindowSize -= dataLength;
state.addAndGetWindow(-dataLength);
frameWriter.writeFrame(frame);
}
/**
* Creates a view of the given frame starting at the current read index with the given number of
* bytes. The reader index on the input frame is then advanced by the number of bytes. The
* returned frame will not have end-of-stream set.
*/
private Http2DataFrame readPartialFrame(Http2DataFrame frame, int numBytes) {
return new DefaultHttp2DataFrame.Builder().setStreamId(frame.getStreamId())
.setContent(frame.content().readSlice(numBytes).retain()).build();
}
/**
* Indicates whether applying the delta to the given value will cause an integer overflow.
*/
private boolean isIntegerOverflow(int previousValue, int delta) {
return delta > 0 && (Integer.MAX_VALUE - delta) < previousValue;
}
/**
* Increments the connectionWindowSize and returns the new value.
*/
private int addAndGetConnectionWindowSize(int delta) throws Http2Exception {
if (isIntegerOverflow(connectionWindowSize, delta)) {
throw format(FLOW_CONTROL_ERROR, "Window update exceeds maximum for connection");
}
return connectionWindowSize += delta;
}
/**
* Writes any pending frames for the entire connection.
*/
private void writePendingFrames() throws Http2Exception {
// The request for for the entire connection, write any pending frames across
// all active streams. Active streams are already sorted by their priority.
for (Http2Stream stream : connection.getActiveStreams()) {
StreamState state = getStateOrFail(stream.getId());
state.writePendingFrames(1);
}
}
/**
* The outbound flow control state for a single stream.
*/
private class StreamState {
private final int streamId;
private final Queue<PendingWrite> pendingWriteQueue = Lists.newLinkedList();
private int windowSize = initialWindowSize;
public StreamState(int streamId) {
this.streamId = streamId;
}
public int addAndGetWindow(int delta) throws Http2Exception {
if (isIntegerOverflow(windowSize, delta)) {
throw new Http2StreamException(streamId, FLOW_CONTROL_ERROR,
"Window size overflow for stream");
}
windowSize += delta;
return windowSize;
}
public int writableWindow() {
return Math.min(windowSize, connectionWindowSize);
}
public void addPendingWrite(PendingWrite pendingWrite) {
pendingWriteQueue.offer(pendingWrite);
}
public boolean hasPendingWrite() {
return !pendingWriteQueue.isEmpty();
}
public PendingWrite peekPendingWrite() {
if (windowSize > 0) {
return pendingWriteQueue.peek();
}
return null;
}
public void removePendingWrite() {
pendingWriteQueue.poll();
}
public void clearPendingWrites() {
while (true) {
PendingWrite pendingWrite = pendingWriteQueue.poll();
if (pendingWrite == null) {
break;
}
pendingWrite.writeError(
format(STREAM_CLOSED, "Stream closed before write could take place"));
}
}
/**
* Sends all pending writes for this stream so long as there is space the the stream and
* connection windows.
*
* @param maxFrames the maximum number of frames to send.
*/
public void writePendingFrames(int maxFrames) throws Http2Exception {
while (maxFrames > 0 && writableWindow() > 0 && hasPendingWrite()) {
maxFrames--;
PendingWrite pendingWrite = peekPendingWrite();
if (writableWindow() >= pendingWrite.size()) {
// Window size is large enough to send entire data frame
removePendingWrite();
writeFrame(pendingWrite.frame(), this, pendingWrite.writer());
} else {
// We can send a partial frame
Http2DataFrame partialDataFrame =
readPartialFrame(pendingWrite.frame(), writableWindow());
writeFrame(partialDataFrame, this, pendingWrite.writer());
}
}
}
}
/**
* Pending write for a single data frame.
*/
private class PendingWrite {
private final Http2DataFrame frame;
private final FrameWriter writer;
public PendingWrite(Http2DataFrame frame, FrameWriter writer) {
this.frame = frame;
this.writer = writer;
}
public Http2DataFrame frame() {
return frame;
}
public FrameWriter writer() {
return writer;
}
public int size() {
return frame.content().readableBytes();
}
public void writeError(Http2Exception cause) {
frame.release();
writer.setFailure(cause);
}
}
}

View File

@ -0,0 +1,174 @@
/*
* 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.connection;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import java.util.List;
public interface Http2Connection {
/**
* A view of the connection from one endpoint (local or remote).
*/
interface Endpoint {
/**
* Creates a stream initiated by this endpoint and notifies all listeners. This could fail for
* the following reasons:
* <p>
* - The requested stream ID is not the next sequential ID for this endpoint. <br>
* - The stream already exists. <br>
* - The number of concurrent streams is above the allowed threshold for this endpoint. <br>
* - The connection is marked as going away}. <br>
* - The provided priority is < 0.
*
* @param streamId The ID of the stream
* @param priority the priority of the stream
* @param halfClosed if true, the stream is created in the half-closed state with respect to
* this endpoint. Otherwise it's created in the open state.
*/
Http2Stream createStream(int streamId, int priority, boolean halfClosed) throws Http2Exception;
/**
* Creates a push stream in the reserved state for this endpoint and notifies all listeners.
* This could fail for the following reasons:
* <p>
* - Server push is not allowed to the opposite endpoint. <br>
* - The requested stream ID is not the next sequential stream ID for this endpoint. <br>
* - The number of concurrent streams is above the allowed threshold for this endpoint. <br>
* - The connection is marked as going away. <br>
* - The parent stream ID does not exist or is not open from the side sending the push promise.
* <br>
* - Could not set a valid priority for the new stream.
*
* @param streamId the ID of the push stream
* @param parent the parent stream used to initiate the push stream.
*/
Http2Stream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception;
/**
* Sets whether server push is allowed to this endpoint.
*/
void setPushToAllowed(boolean allow);
/**
* Gets whether or not server push is allowed to this endpoint.
*/
boolean isPushToAllowed();
/**
* Gets the maximum number of concurrent streams allowed by this endpoint.
*/
int getMaxStreams();
/**
* Sets the maximum number of concurrent streams allowed by this endpoint.
*/
void setMaxStreams(int maxStreams);
/**
* Gets the ID of the stream last successfully created by this endpoint.
*/
int getLastStreamCreated();
/**
* Gets the {@link Endpoint} opposite this one.
*/
Endpoint opposite();
}
/**
* A listener of the connection for stream events.
*/
interface Listener {
/**
* Called when a new stream with the given ID is created.
*/
void streamCreated(int streamId);
/**
* Called when the stream with the given ID is closed.
*/
void streamClosed(int streamId);
}
/**
* Adds a listener of this connection.
*/
void addListener(Listener listener);
/**
* Removes a listener of this connection.
*/
void removeListener(Listener listener);
/**
* Attempts to get the stream for the given ID. If it doesn't exist, throws.
*/
Http2Stream getStreamOrFail(int streamId) throws Http2Exception;
/**
* Gets the stream if it exists. If not, returns {@code null}.
*/
Http2Stream getStream(int streamId);
/**
* Gets all streams that are currently either open or half-closed. The returned collection is
* sorted by priority.
*/
List<Http2Stream> getActiveStreams();
/**
* Gets a view of this connection from the local {@link Endpoint}.
*/
Endpoint local();
/**
* Gets a view of this connection from the remote {@link Endpoint}.
*/
Endpoint remote();
/**
* Marks that a GoAway frame has been sent on this connection. After calling this, both
* {@link #isGoAwaySent()} and {@link #isGoAway()} will be {@code true}.
*/
void sendGoAway(ChannelHandlerContext ctx, ChannelPromise promise, Http2Exception cause);
/**
* Marks that a GoAway frame has been received on this connection. After calling this, both
* {@link #isGoAwayReceived()} and {@link #isGoAway()} will be {@code true}.
*/
void goAwayReceived();
/**
* Indicates that this connection received a GoAway message.
*/
boolean isGoAwaySent();
/**
* Indicates that this connection send a GoAway message.
*/
boolean isGoAwayReceived();
/**
* Indicates whether or not this endpoint is going away. This is a short form for
* {@link #isGoAwaySent()} || {@link #isGoAwayReceived()}.
*/
boolean isGoAway();
}

View File

@ -0,0 +1,560 @@
/*
* 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.connection;
import static io.netty.handler.codec.http2.draft10.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.draft10.Http2Error.STREAM_CLOSED;
import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.toHttp2Exception;
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.HALF_CLOSED_LOCAL;
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.HALF_CLOSED_REMOTE;
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.OPEN;
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_LOCAL;
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_REMOTE;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2StreamException;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2PingFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2RstStreamFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2SettingsFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2GoAwayFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2HeadersFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2PingFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2PriorityFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2PushPromiseFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2RstStreamFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2SettingsFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2StreamFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
import io.netty.util.ReferenceCountUtil;
public class Http2ConnectionHandler extends ChannelHandlerAdapter {
private final Http2Connection connection;
private final InboundFlowController inboundFlow;
private final OutboundFlowController outboundFlow;
public Http2ConnectionHandler(boolean server) {
this(new DefaultHttp2Connection(server));
}
public Http2ConnectionHandler(Http2Connection connection) {
this(connection, new DefaultInboundFlowController(connection),
new DefaultOutboundFlowController(connection));
}
public Http2ConnectionHandler(final Http2Connection connection,
final InboundFlowController inboundFlow, final OutboundFlowController outboundFlow) {
this.connection = connection;
this.inboundFlow = inboundFlow;
this.outboundFlow = outboundFlow;
}
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
// Avoid NotYetConnectedException
if (!ctx.channel().isActive()) {
ctx.close(promise);
return;
}
connection.sendGoAway(ctx, promise, null);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
for (Http2Stream stream : connection.getActiveStreams()) {
stream.close(ctx, ctx.newSucceededFuture());
}
ctx.fireChannelInactive();
}
/**
* Handles {@link Http2Exception} objects that were thrown from other handlers. Ignores all other
* exceptions.
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof Http2Exception) {
processHttp2Exception(ctx, (Http2Exception) cause);
}
ctx.fireExceptionCaught(cause);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object inMsg) throws Exception {
try {
if (inMsg instanceof Http2DataFrame) {
handleInboundData(ctx, (Http2DataFrame) inMsg);
} else if (inMsg instanceof Http2HeadersFrame) {
handleInboundHeaders(ctx, (Http2HeadersFrame) inMsg);
} else if (inMsg instanceof Http2PushPromiseFrame) {
handleInboundPushPromise(ctx, (Http2PushPromiseFrame) inMsg);
} else if (inMsg instanceof Http2PriorityFrame) {
handleInboundPriority(ctx, (Http2PriorityFrame) inMsg);
} else if (inMsg instanceof Http2RstStreamFrame) {
handleInboundRstStream(ctx, (Http2RstStreamFrame) inMsg);
} else if (inMsg instanceof Http2PingFrame) {
handleInboundPing(ctx, (Http2PingFrame) inMsg);
} else if (inMsg instanceof Http2GoAwayFrame) {
handleInboundGoAway(ctx, (Http2GoAwayFrame) inMsg);
} else if (inMsg instanceof Http2WindowUpdateFrame) {
handleInboundWindowUpdate(ctx, (Http2WindowUpdateFrame) inMsg);
} else if (inMsg instanceof Http2SettingsFrame) {
handleInboundSettings(ctx, (Http2SettingsFrame) inMsg);
} else {
ctx.fireChannelRead(inMsg);
}
} catch (Http2Exception e) {
ReferenceCountUtil.release(inMsg);
processHttp2Exception(ctx, e);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
try {
if (msg instanceof Http2DataFrame) {
handleOutboundData(ctx, (Http2DataFrame) msg, promise);
} else if (msg instanceof Http2HeadersFrame) {
handleOutboundHeaders(ctx, (Http2HeadersFrame) msg, promise);
} else if (msg instanceof Http2PushPromiseFrame) {
handleOutboundPushPromise(ctx, (Http2PushPromiseFrame) msg, promise);
} else if (msg instanceof Http2PriorityFrame) {
handleOutboundPriority(ctx, (Http2PriorityFrame) msg, promise);
} else if (msg instanceof Http2RstStreamFrame) {
handleOutboundRstStream(ctx, (Http2RstStreamFrame) msg, promise);
} else if (msg instanceof Http2PingFrame) {
handleOutboundPing(ctx, (Http2PingFrame) msg, promise);
} else if (msg instanceof Http2GoAwayFrame) {
handleOutboundGoAway();
} else if (msg instanceof Http2WindowUpdateFrame) {
handleOutboundWindowUpdate();
} else if (msg instanceof Http2SettingsFrame) {
handleOutboundSettings(ctx, (Http2SettingsFrame) msg, promise);
} else {
ctx.write(msg, promise);
return;
}
} catch (Throwable e) {
ReferenceCountUtil.release(msg);
promise.setFailure(e);
}
}
/**
* Processes the given exception. Depending on the type of exception, delegates to either
* {@link #processConnectionError} or {@link #processStreamError}.
*/
private void processHttp2Exception(ChannelHandlerContext ctx, Http2Exception e) {
if (e instanceof Http2StreamException) {
processStreamError(ctx, (Http2StreamException) e);
} else {
processConnectionError(ctx, e);
}
}
private void processConnectionError(ChannelHandlerContext ctx, Http2Exception cause) {
connection.sendGoAway(ctx, ctx.newPromise(), cause);
}
private void processStreamError(ChannelHandlerContext ctx, Http2StreamException cause) {
// Close the stream if it was open.
int streamId = cause.getStreamId();
ChannelPromise promise = ctx.newPromise();
Http2Stream stream = connection.getStream(streamId);
if (stream != null) {
stream.close(ctx, promise);
}
// Send the Rst frame to the remote endpoint.
Http2RstStreamFrame frame = new DefaultHttp2RstStreamFrame.Builder().setStreamId(streamId)
.setErrorCode(cause.getError().getCode()).build();
ctx.writeAndFlush(frame, promise);
}
private void handleInboundData(final ChannelHandlerContext ctx, Http2DataFrame frame)
throws Http2Exception {
// Check if we received a data frame for a stream which is half-closed
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
// Apply flow control.
inboundFlow.applyInboundFlowControl(frame, new InboundFlowController.FrameWriter() {
@Override
public void writeFrame(Http2WindowUpdateFrame frame) {
ctx.writeAndFlush(frame);
}
});
if (isInboundStreamAfterGoAway(frame)) {
// Ignore frames for any stream created after we sent a go-away.
frame.release();
return;
}
if (frame.isEndOfStream()) {
stream.closeRemoteSide(ctx, ctx.newSucceededFuture());
}
// Allow this frame to continue other handlers.
ctx.fireChannelRead(frame);
}
private void handleInboundHeaders(ChannelHandlerContext ctx, Http2HeadersFrame frame)
throws Http2Exception {
if (isInboundStreamAfterGoAway(frame)) {
return;
}
int streamId = frame.getStreamId();
Http2Stream stream = connection.getStream(streamId);
if (stream == null) {
// Create the new stream.
connection.remote().createStream(frame.getStreamId(), frame.getPriority(),
frame.isEndOfStream());
} else {
// If the stream already exists, it must be a reserved push stream. If so, open
// it for push to the local endpoint.
stream.verifyState(PROTOCOL_ERROR, RESERVED_REMOTE);
stream.openForPush();
// If the headers completes this stream, close it.
if (frame.isEndOfStream()) {
stream.closeRemoteSide(ctx, ctx.newSucceededFuture());
}
}
ctx.fireChannelRead(frame);
}
private void handleInboundPushPromise(ChannelHandlerContext ctx, Http2PushPromiseFrame frame)
throws Http2Exception {
if (isInboundStreamAfterGoAway(frame)) {
// Ignore frames for any stream created after we sent a go-away.
return;
}
// Reserve the push stream based with a priority based on the current stream's priority.
Http2Stream parentStream = connection.getStreamOrFail(frame.getStreamId());
connection.remote().reservePushStream(frame.getPromisedStreamId(), parentStream);
ctx.fireChannelRead(frame);
}
private void handleInboundPriority(ChannelHandlerContext ctx, Http2PriorityFrame frame)
throws Http2Exception {
if (isInboundStreamAfterGoAway(frame)) {
// Ignore frames for any stream created after we sent a go-away.
return;
}
Http2Stream stream = connection.getStream(frame.getStreamId());
if (stream == null) {
// Priority frames must be ignored for closed streams.
return;
}
stream.verifyState(PROTOCOL_ERROR, HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE, OPEN, RESERVED_LOCAL);
// Set the priority on the frame.
stream.setPriority(frame.getPriority());
ctx.fireChannelRead(frame);
}
private void handleInboundWindowUpdate(ChannelHandlerContext ctx, Http2WindowUpdateFrame frame)
throws Http2Exception {
if (isInboundStreamAfterGoAway(frame)) {
// Ignore frames for any stream created after we sent a go-away.
return;
}
int streamId = frame.getStreamId();
if (streamId > 0) {
Http2Stream stream = connection.getStream(streamId);
if (stream == null) {
// Window Update frames must be ignored for closed streams.
return;
}
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
}
// Update the outbound flow controller.
outboundFlow.updateOutboundWindowSize(streamId, frame.getWindowSizeIncrement());
ctx.fireChannelRead(frame);
}
private void handleInboundRstStream(ChannelHandlerContext ctx, Http2RstStreamFrame frame) {
if (isInboundStreamAfterGoAway(frame)) {
// Ignore frames for any stream created after we sent a go-away.
return;
}
Http2Stream stream = connection.getStream(frame.getStreamId());
if (stream == null) {
// RstStream frames must be ignored for closed streams.
return;
}
stream.close(ctx, ctx.newSucceededFuture());
ctx.fireChannelRead(frame);
}
private void handleInboundPing(ChannelHandlerContext ctx, Http2PingFrame frame) {
if (frame.isAck()) {
// The remote enpoint is responding to an Ack that we sent.
ctx.fireChannelRead(frame);
return;
}
// The remote endpoint is sending the ping. Acknowledge receipt.
DefaultHttp2PingFrame ack = new DefaultHttp2PingFrame.Builder().setAck(true)
.setData(frame.content().duplicate().retain()).build();
ctx.writeAndFlush(ack);
}
private void handleInboundSettings(ChannelHandlerContext ctx, Http2SettingsFrame frame)
throws Http2Exception {
if (frame.isAck()) {
// The remote endpoint is acknowledging the settings - fire this up to the next
// handler.
ctx.fireChannelRead(frame);
return;
}
// It's not an ack, apply the settings.
if (frame.getHeaderTableSize() != null) {
// TODO(nathanmittler): what's the right thing handle this?
// headersEncoder.setHeaderTableSize(frame.getHeaderTableSize());
}
if (frame.getPushEnabled() != null) {
connection.remote().setPushToAllowed(frame.getPushEnabled());
}
if (frame.getMaxConcurrentStreams() != null) {
int value = Math.max(0, (int) Math.min(Integer.MAX_VALUE, frame.getMaxConcurrentStreams()));
connection.local().setMaxStreams(value);
}
if (frame.getInitialWindowSize() != null) {
outboundFlow.setInitialOutboundWindowSize(frame.getInitialWindowSize());
}
// Acknowledge receipt of the settings.
Http2Frame ack = new DefaultHttp2SettingsFrame.Builder().setAck(true).build();
ctx.writeAndFlush(ack);
}
private void handleInboundGoAway(ChannelHandlerContext ctx, Http2GoAwayFrame frame) {
// Don't allow any more connections to be created.
connection.goAwayReceived();
ctx.fireChannelRead(frame);
}
/**
* Determines whether or not the stream was created after we sent a go-away frame. Frames from
* streams created after we sent a go-away should be ignored. Frames for the connection stream ID
* (i.e. 0) will always be allowed.
*/
private boolean isInboundStreamAfterGoAway(Http2StreamFrame frame) {
return connection.isGoAwaySent()
&& connection.remote().getLastStreamCreated() <= frame.getStreamId();
}
private void handleOutboundData(final ChannelHandlerContext ctx, Http2DataFrame frame,
final ChannelPromise promise) throws Http2Exception {
if (connection.isGoAway()) {
throw format(PROTOCOL_ERROR, "Sending data after connection going away.");
}
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
// Hand control of the frame to the flow controller.
outboundFlow.sendFlowControlled(frame, new OutboundFlowController.FrameWriter() {
@Override
public void writeFrame(Http2DataFrame frame) {
ChannelFuture future = ctx.writeAndFlush(frame, promise);
// Close the connection on write failures that leave the outbound flow control
// window in a corrupt state.
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
processHttp2Exception(ctx, toHttp2Exception(future.cause()));
}
}
});
// Close the local side of the stream if this is the last frame
if (frame.isEndOfStream()) {
Http2Stream stream = connection.getStream(frame.getStreamId());
stream.closeLocalSide(ctx, promise);
}
}
@Override
public void setFailure(Throwable cause) {
promise.setFailure(cause);
}
});
}
private void handleOutboundHeaders(ChannelHandlerContext ctx, Http2HeadersFrame frame,
ChannelPromise promise) throws Http2Exception {
if (connection.isGoAway()) {
throw format(PROTOCOL_ERROR, "Sending headers after connection going away.");
}
Http2Stream stream = connection.getStream(frame.getStreamId());
if (stream == null) {
// Creates a new locally-initiated stream.
stream = connection.local().createStream(frame.getStreamId(), frame.getPriority(),
frame.isEndOfStream());
} else {
// If the stream already exists, it must be a reserved push stream. If so, open
// it for push to the remote endpoint.
stream.verifyState(PROTOCOL_ERROR, RESERVED_LOCAL);
stream.openForPush();
// If the headers are the end of the stream, close it now.
if (frame.isEndOfStream()) {
stream.closeLocalSide(ctx, promise);
}
}
// Flush to send all of the frames.
ctx.writeAndFlush(frame, promise);
}
private void handleOutboundPushPromise(ChannelHandlerContext ctx, Http2PushPromiseFrame frame,
ChannelPromise promise) throws Http2Exception {
if (connection.isGoAway()) {
throw format(PROTOCOL_ERROR, "Sending push promise after connection going away.");
}
// Reserve the promised stream.
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
connection.local().reservePushStream(frame.getPromisedStreamId(), stream);
// Write the frame.
ctx.writeAndFlush(frame, promise);
}
private void handleOutboundPriority(ChannelHandlerContext ctx, Http2PriorityFrame frame,
ChannelPromise promise) throws Http2Exception {
if (connection.isGoAway()) {
throw format(PROTOCOL_ERROR, "Sending priority after connection going away.");
}
// Set the priority on the stream and forward the frame.
Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
stream.setPriority(frame.getPriority());
ctx.writeAndFlush(frame, promise);
}
private void handleOutboundRstStream(ChannelHandlerContext ctx, Http2RstStreamFrame frame,
ChannelPromise promise) {
Http2Stream stream = connection.getStream(frame.getStreamId());
if (stream == null) {
// The stream may already have been closed ... ignore.
promise.setSuccess();
return;
}
stream.close(ctx, promise);
ctx.writeAndFlush(frame, promise);
}
private void handleOutboundPing(ChannelHandlerContext ctx, Http2PingFrame frame,
ChannelPromise promise) throws Http2Exception {
if (connection.isGoAway()) {
throw format(PROTOCOL_ERROR, "Sending ping after connection going away.");
}
if (frame.isAck()) {
throw format(PROTOCOL_ERROR, "Another handler attempting to send ping ack");
}
// Just pass the frame through.
ctx.writeAndFlush(frame, promise);
}
private void handleOutboundGoAway() throws Http2Exception {
// Why is this being sent? Intercept it and fail the write.
// Should have sent a CLOSE ChannelStateEvent
throw format(PROTOCOL_ERROR, "Another handler attempted to send GoAway.");
}
private void handleOutboundWindowUpdate() throws Http2Exception {
// Why is this being sent? Intercept it and fail the write.
throw format(PROTOCOL_ERROR, "Another handler attempted to send window update.");
}
private void handleOutboundSettings(ChannelHandlerContext ctx, Http2SettingsFrame frame,
ChannelPromise promise) throws Http2Exception {
if (connection.isGoAway()) {
throw format(PROTOCOL_ERROR, "Sending settings after connection going away.");
}
if (frame.isAck()) {
throw format(PROTOCOL_ERROR, "Another handler attempting to send settings ack");
}
if (frame.getPushEnabled() != null) {
// Enable/disable server push to this endpoint.
connection.local().setPushToAllowed(frame.getPushEnabled());
}
if (frame.getHeaderTableSize() != null) {
// TODO(nathanmittler): what's the right way to handle this?
// headersDecoder.setHeaderTableSize(frame.getHeaderTableSize());
}
if (frame.getMaxConcurrentStreams() != null) {
// Update maximum number of streams the remote endpoint can initiate.
if (frame.getMaxConcurrentStreams() < 0L
|| frame.getMaxConcurrentStreams() > Integer.MAX_VALUE) {
throw format(PROTOCOL_ERROR, "Invalid value for max concurrent streams: %d",
frame.getMaxConcurrentStreams());
}
connection.remote().setMaxStreams(frame.getMaxConcurrentStreams().intValue());
}
if (frame.getInitialWindowSize() != null) {
// Update the initial window size for inbound traffic.
if (frame.getInitialWindowSize() < 0) {
throw format(PROTOCOL_ERROR, "Invalid value for initial window size: %d",
frame.getInitialWindowSize());
}
inboundFlow.setInitialInboundWindowSize(frame.getInitialWindowSize());
}
ctx.writeAndFlush(frame, promise);
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.connection;
import static io.netty.handler.codec.http2.draft10.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http2.draft10.Http2Exception;
/**
* Constants and utility method used for encoding/decoding HTTP2 frames.
*/
public final class Http2ConnectionUtil {
public static final int DEFAULT_FLOW_CONTROL_WINDOW_SIZE = 65535;
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
public static final int DEFAULT_MAX_HEADER_SIZE = 4096;
/**
* Converts the given cause to a {@link Http2Exception} if it isn't already.
*/
public static Http2Exception toHttp2Exception(Throwable cause) {
if (cause instanceof Http2Exception) {
return (Http2Exception) cause;
}
String msg = cause != null ? cause.getMessage() : "Failed writing the data frame.";
return format(INTERNAL_ERROR, msg);
}
/**
* Creates a buffer containing the error message from the given exception. If the cause is
* {@code null} returns an empty buffer.
*/
public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
ByteBuf debugData = Unpooled.EMPTY_BUFFER;
if (cause != null) {
// Create the debug message.
byte[] msg = cause.getMessage().getBytes();
debugData = ctx.alloc().buffer(msg.length);
debugData.writeBytes(msg);
}
return debugData;
}
private Http2ConnectionUtil() {
}
}

View File

@ -0,0 +1,95 @@
/*
* 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.connection;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http2.draft10.Http2Error;
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> {
/**
* The allowed states of an HTTP2 stream.
*/
enum State {
IDLE, RESERVED_LOCAL, RESERVED_REMOTE, OPEN, HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE, CLOSED;
}
/**
* Gets the unique identifier for this stream within the connection.
*/
int getId();
/**
* Gets the state of this stream.
*/
State getState();
/**
* Verifies that the stream is in one of the given allowed states.
*/
void verifyState(Http2Error error, State... allowedStates) throws Http2Exception;
/**
* Sets the priority of this stream. A value of zero is the highest priority and a value of
* {@link Integer#MAX_VALUE} is the lowest.
*/
void setPriority(int priority) throws Http2Exception;
/**
* Gets the priority of this stream. A value of zero is the highest priority and a value of
* {@link Integer#MAX_VALUE} is the lowest.
*/
int getPriority();
/**
* If this is a reserved push stream, opens the stream for push in one direction.
*/
void openForPush() throws Http2Exception;
/**
* Closes the stream.
*/
void close(ChannelHandlerContext ctx, ChannelFuture future);
/**
* Closes the local side of this stream. If this makes the stream closed, the child is closed as
* well.
*/
void closeLocalSide(ChannelHandlerContext ctx, ChannelFuture future);
/**
* Closes the remote side of this stream. If this makes the stream closed, the child is closed as
* well.
*/
void closeRemoteSide(ChannelHandlerContext ctx, ChannelFuture future);
/**
* Indicates whether the remote side of this stream is open (i.e. the state is either
* {@link State#OPEN} or {@link State#HALF_CLOSED_LOCAL}).
*/
boolean isRemoteSideOpen();
/**
* Indicates whether the local side of this stream is open (i.e. the state is either
* {@link State#OPEN} or {@link State#HALF_CLOSED_REMOTE}).
*/
boolean isLocalSideOpen();
}

View File

@ -0,0 +1,56 @@
/*
* 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.connection;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
/**
* Controls the inbound flow of data frames from the remote endpoint.
*/
public interface InboundFlowController {
/**
* A writer of window update frames.
*/
interface FrameWriter {
/**
* Writes a window update frame to the remote endpoint.
*/
void writeFrame(Http2WindowUpdateFrame frame);
}
/**
* Sets the initial inbound flow control window size and updates all stream window sizes by the
* delta. This is called as part of the processing for an outbound SETTINGS frame.
*
* @param newWindowSize the new initial window size.
* @throws Http2Exception thrown if any protocol-related error occurred.
*/
void setInitialInboundWindowSize(int newWindowSize) throws Http2Exception;
/**
* Applies flow control for the received data frame.
*
* @param dataFrame the flow controlled frame
* @param frameWriter allows this flow controller to send window updates to the remote endpoint.
* @throws Http2Exception thrown if any protocol-related error occurred.
*/
void applyInboundFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
throws Http2Exception;
}

View File

@ -0,0 +1,79 @@
/*
* 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.connection;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
/**
* Controls the outbound flow of data frames to the remote endpoint.
*/
public interface OutboundFlowController {
/**
* Interface that abstracts the writing of {@link Http2Frame} objects to the remote endpoint.
*/
interface FrameWriter {
/**
* Writes a single data frame to the remote endpoint.
*/
void writeFrame(Http2DataFrame frame);
/**
* Called if an error occurred before the write could take place. Sets the failure on the
* channel promise.
*/
void setFailure(Throwable cause);
}
/**
* Sets the initial size of the connection's outbound flow control window. The outbound flow
* control windows for all streams are updated by the delta in the initial window size. This is
* called as part of the processing of a SETTINGS frame received from the remote endpoint.
*
* @param newWindowSize the new initial window size.
*/
void setInitialOutboundWindowSize(int newWindowSize) throws Http2Exception;
/**
* Updates the size of the stream's outbound flow control window. This is called upon receiving a
* WINDOW_UPDATE frame from the remote endpoint.
*
* @param streamId the ID of the stream, or zero if the window is for the entire connection.
* @param deltaWindowSize the change in size of the outbound flow control window.
* @throws Http2Exception thrown if a protocol-related error occurred.
*/
void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception;
/**
* Sends the frame with outbound flow control applied. The frame may be written at a later time,
* depending on whether the remote endpoint can receive the frame now.
* <p>
* Data frame flow control processing requirements:
* <p>
* Sender must not send a data frame with data length greater than the transfer window size. After
* sending each data frame, the stream's transfer window size is decremented by the amount of data
* transmitted. When the window size becomes less than or equal to 0, the sender must pause
* transmitting data frames.
*
* @param frame the frame to send.
* @param frameWriter peforms to the write of the frame to the remote endpoint.
* @throws Http2Exception thrown if a protocol-related error occurred.
*/
void sendFlowControlled(Http2DataFrame frame, FrameWriter frameWriter) throws Http2Exception;
}

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Connection-level services (stream management, flow control) for HTTP2.
*/
package io.netty.handler.codec.http2.draft10.connection;

View File

@ -0,0 +1,190 @@
/*
* 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.frame;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_SHORT;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
import io.netty.buffer.Unpooled;
/**
* Default implementation of {@link Http2DataFrame}.
*/
public final class DefaultHttp2DataFrame extends DefaultByteBufHolder implements Http2DataFrame {
private final int paddingLength;
private final int streamId;
private final boolean endOfStream;
private DefaultHttp2DataFrame(Builder builder) {
super(builder.content);
this.streamId = builder.streamId;
this.endOfStream = builder.endOfStream;
this.paddingLength = builder.paddingLength;
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public boolean isEndOfStream() {
return endOfStream;
}
@Override
public int getPaddingLength() {
return paddingLength;
}
@Override
public DefaultHttp2DataFrame copy() {
return copyBuilder().setContent(content().copy()).build();
}
@Override
public DefaultHttp2DataFrame duplicate() {
return copyBuilder().setContent(content().duplicate()).build();
}
@Override
public DefaultHttp2DataFrame retain() {
super.retain();
return this;
}
@Override
public DefaultHttp2DataFrame retain(int increment) {
super.retain(increment);
return this;
}
@Override
public DefaultHttp2DataFrame touch() {
super.touch();
return this;
}
@Override
public DefaultHttp2DataFrame touch(Object hint) {
super.touch(hint);
return this;
}
@Override
public int hashCode() {
final int prime = 31;
int result = content().hashCode();
result = prime * result + (endOfStream ? 1231 : 1237);
result = prime * result + paddingLength;
result = prime * result + streamId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2DataFrame other = (DefaultHttp2DataFrame) obj;
if (endOfStream != other.endOfStream) {
return false;
}
if (paddingLength != other.paddingLength) {
return false;
}
if (streamId != other.streamId) {
return false;
}
if (!content().equals(other.content())) {
return false;
}
return true;
}
private Builder copyBuilder() {
return new Builder().setStreamId(streamId).setPaddingLength(paddingLength)
.setEndOfStream(endOfStream);
}
/**
* Builds instances of {@link DefaultHttp2DataFrame}.
*/
public static class Builder {
private int streamId;
private boolean endOfStream;
private ByteBuf content = Unpooled.EMPTY_BUFFER;
private int paddingLength;
public Builder setStreamId(int streamId) {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be > 0.");
}
this.streamId = streamId;
return this;
}
public Builder setEndOfStream(boolean endOfStream) {
this.endOfStream = endOfStream;
return this;
}
/**
* Sets the content for the data frame, excluding any padding. This buffer will be retained when
* the frame is built.
*/
public Builder setContent(ByteBuf content) {
if (content == null) {
throw new IllegalArgumentException("content must not be null");
}
verifyLength(paddingLength, content);
this.content = content;
return this;
}
public Builder setPaddingLength(int paddingLength) {
if (paddingLength < 0 || paddingLength > MAX_UNSIGNED_SHORT) {
throw new IllegalArgumentException("Padding length invalid.");
}
verifyLength(paddingLength, content);
this.paddingLength = paddingLength;
return this;
}
public DefaultHttp2DataFrame build() {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be set.");
}
verifyLength(paddingLength, content);
return new DefaultHttp2DataFrame(this);
}
private void verifyLength(int paddingLength, ByteBuf data) {
int maxLength = MAX_FRAME_PAYLOAD_LENGTH;
maxLength -= paddingLength;
if (data.readableBytes() > maxLength) {
throw new IllegalArgumentException("Header block fragment length too big.");
}
}
}
}

View File

@ -0,0 +1,163 @@
/*
* 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.frame;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_INT;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
import io.netty.buffer.Unpooled;
/**
* Default implementation of {@link Http2GoAwayFrame}.
*/
public final class DefaultHttp2GoAwayFrame extends DefaultByteBufHolder implements
Http2GoAwayFrame {
private final int lastStreamId;
private final long errorCode;
private DefaultHttp2GoAwayFrame(Builder builder) {
super(builder.debugData);
this.lastStreamId = builder.lastStreamId;
this.errorCode = builder.errorCode;
}
@Override
public int getLastStreamId() {
return lastStreamId;
}
@Override
public long getErrorCode() {
return errorCode;
}
@Override
public DefaultHttp2GoAwayFrame copy() {
return copyBuilder().setDebugData(content().copy()).build();
}
@Override
public DefaultHttp2GoAwayFrame duplicate() {
return copyBuilder().setDebugData(content().duplicate()).build();
}
@Override
public DefaultHttp2GoAwayFrame retain() {
super.retain();
return this;
}
@Override
public DefaultHttp2GoAwayFrame retain(int increment) {
super.retain(increment);
return this;
}
@Override
public DefaultHttp2GoAwayFrame touch() {
super.touch();
return this;
}
@Override
public DefaultHttp2GoAwayFrame touch(Object hint) {
super.touch(hint);
return this;
}
@Override
public int hashCode() {
final int prime = 31;
int result = content().hashCode();
result = prime * result + (int) (errorCode ^ (errorCode >>> 32));
result = prime * result + lastStreamId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2GoAwayFrame other = (DefaultHttp2GoAwayFrame) obj;
if (errorCode != other.errorCode) {
return false;
}
if (lastStreamId != other.lastStreamId) {
return false;
}
if (!content().equals(other.content())) {
return false;
}
return true;
}
private Builder copyBuilder() {
return new Builder().setErrorCode(errorCode).setLastStreamId(lastStreamId);
}
/**
* Builds instances of {@link DefaultHttp2GoAwayFrame}.
*/
public static class Builder {
private int lastStreamId = -1;
private long errorCode = -1;
private ByteBuf debugData = Unpooled.EMPTY_BUFFER;
public Builder setLastStreamId(int lastStreamId) {
if (lastStreamId < 0) {
throw new IllegalArgumentException("Invalid lastStreamId.");
}
this.lastStreamId = lastStreamId;
return this;
}
public Builder setErrorCode(long errorCode) {
if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
throw new IllegalArgumentException("Invalid error code.");
}
this.errorCode = errorCode;
return this;
}
public Builder setDebugData(ByteBuf debugData) {
if (debugData == null) {
throw new IllegalArgumentException("debugData must not be null");
}
if (debugData.readableBytes() > MAX_FRAME_PAYLOAD_LENGTH - 8) {
throw new IllegalArgumentException("Invalid debug data size.");
}
this.debugData = debugData;
return this;
}
public DefaultHttp2GoAwayFrame build() {
if (lastStreamId < 0) {
throw new IllegalArgumentException("LastStreamId must be set");
}
if (errorCode < 0) {
throw new IllegalArgumentException("ErrorCode must be set.");
}
return new DefaultHttp2GoAwayFrame(this);
}
}
}

View File

@ -0,0 +1,146 @@
/*
* 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.frame;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
import io.netty.handler.codec.http2.draft10.Http2Headers;
public final class DefaultHttp2HeadersFrame implements Http2HeadersFrame {
private final int streamId;
private final int priority;
private final boolean endOfStream;
private final Http2Headers headers;
private DefaultHttp2HeadersFrame(Builder builder) {
this.streamId = builder.streamId;
this.priority = builder.priority;
this.headers = builder.headersBuilder.build();
this.endOfStream = builder.endOfStream;
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public boolean isEndOfStream() {
return endOfStream;
}
@Override
public int getPriority() {
return priority;
}
@Override
public Http2Headers getHeaders() {
return headers;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (endOfStream ? 1231 : 1237);
result = prime * result + ((headers == null) ? 0 : headers.hashCode());
result = prime * result + priority;
result = prime * result + streamId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2HeadersFrame other = (DefaultHttp2HeadersFrame) obj;
if (endOfStream != other.endOfStream) {
return false;
}
if (headers == null) {
if (other.headers != null) {
return false;
}
} else if (!headers.equals(other.headers)) {
return false;
}
if (priority != other.priority) {
return false;
}
if (streamId != other.streamId) {
return false;
}
return true;
}
@Override
public String toString() {
return "DefaultHttp2HeadersFrame [streamId=" + streamId + ", priority=" + priority
+ ", endOfStream=" + endOfStream + ", headers=" + headers + "]";
}
public static class Builder {
private int streamId;
private int priority = DEFAULT_STREAM_PRIORITY;
private Http2Headers.Builder headersBuilder = new Http2Headers.Builder();
private boolean endOfStream;
public Builder setStreamId(int streamId) {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be > 0.");
}
this.streamId = streamId;
return this;
}
public Builder setEndOfStream(boolean endOfStream) {
this.endOfStream = endOfStream;
return this;
}
public Builder setPriority(int priority) {
if (priority < 0) {
throw new IllegalArgumentException("Priority must be >= 0");
}
this.priority = priority;
return this;
}
public Http2Headers.Builder headers() {
return headersBuilder;
}
public Builder setHeaders(Http2Headers headers) {
this.headersBuilder.addHeaders(headers);
return this;
}
public DefaultHttp2HeadersFrame build() {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be set.");
}
return new DefaultHttp2HeadersFrame(this);
}
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.frame;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
/**
* Default implementation of {@link Http2PingFrame}.
*/
public final class DefaultHttp2PingFrame extends DefaultByteBufHolder implements Http2PingFrame {
private final boolean ack;
private DefaultHttp2PingFrame(Builder builder) {
super(builder.data);
this.ack = builder.ack;
}
@Override
public boolean isAck() {
return ack;
}
@Override
public DefaultHttp2PingFrame copy() {
return new Builder().setAck(ack).setData(content().copy()).build();
}
@Override
public DefaultHttp2PingFrame duplicate() {
return new Builder().setAck(ack).setData(content().duplicate()).build();
}
@Override
public DefaultHttp2PingFrame retain() {
super.retain();
return this;
}
@Override
public DefaultHttp2PingFrame retain(int increment) {
super.retain(increment);
return this;
}
@Override
public DefaultHttp2PingFrame touch() {
super.touch();
return this;
}
@Override
public DefaultHttp2PingFrame touch(Object hint) {
super.touch(hint);
return this;
}
@Override
public int hashCode() {
final int prime = 31;
int result = content().hashCode();
result = prime * result + (ack ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2PingFrame other = (DefaultHttp2PingFrame) obj;
if (ack != other.ack) {
return false;
}
if (!content().equals(other.content())) {
return false;
}
return true;
}
/**
* Builds instances of {@link DefaultHttp2PingFrame}.
*/
public static class Builder {
private boolean ack;
private ByteBuf data;
/**
* Sets the data for this ping. This buffer will be retained when the frame is built.
*/
public Builder setData(ByteBuf data) {
if (data == null) {
throw new IllegalArgumentException("data must not be null.");
}
if (data.readableBytes() != PING_FRAME_PAYLOAD_LENGTH) {
throw new IllegalArgumentException(String.format(
"Incorrect data length for ping. Expected %d, found %d",
PING_FRAME_PAYLOAD_LENGTH, data.readableBytes()));
}
this.data = data;
return this;
}
public Builder setAck(boolean ack) {
this.ack = ack;
return this;
}
public DefaultHttp2PingFrame build() {
if (data == null) {
throw new IllegalArgumentException("debug data must be provided");
}
return new DefaultHttp2PingFrame(this);
}
}
}

View File

@ -0,0 +1,109 @@
/*
* 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.frame;
/**
* Default implementation of {@link Http2PriorityFrame}.
*/
public final class DefaultHttp2PriorityFrame implements Http2PriorityFrame {
private final int streamId;
private final int priority;
private DefaultHttp2PriorityFrame(Builder builder) {
this.streamId = builder.streamId;
this.priority = builder.priority;
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public int getPriority() {
return priority;
}
@Override
public boolean isEndOfStream() {
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + priority;
result = prime * result + streamId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2PriorityFrame other = (DefaultHttp2PriorityFrame) obj;
if (priority != other.priority) {
return false;
}
if (streamId != other.streamId) {
return false;
}
return true;
}
/**
* Builds instances of {@link DefaultHttp2PriorityFrame}.
*/
public static class Builder {
private int streamId;
private int priority = -1;
public Builder setStreamId(int streamId) {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be > 0.");
}
this.streamId = streamId;
return this;
}
public Builder setPriority(int priority) {
if (priority < 0) {
throw new IllegalArgumentException("Invalid priority.");
}
this.priority = priority;
return this;
}
public DefaultHttp2PriorityFrame build() {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be set.");
}
if (priority < 0) {
throw new IllegalArgumentException("Priority must be set.");
}
return new DefaultHttp2PriorityFrame(this);
}
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.frame;
import io.netty.handler.codec.http2.draft10.Http2Headers;
public final class DefaultHttp2PushPromiseFrame implements Http2PushPromiseFrame {
private final int streamId;
private final int promisedStreamId;
private final Http2Headers headers;
private DefaultHttp2PushPromiseFrame(Builder builder) {
this.streamId = builder.streamId;
this.promisedStreamId = builder.promisedStreamId;
this.headers = builder.headers;
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public boolean isEndOfStream() {
return false;
}
@Override
public int getPromisedStreamId() {
return promisedStreamId;
}
@Override
public Http2Headers getHeaders() {
return headers;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((headers == null) ? 0 : headers.hashCode());
result = prime * result + promisedStreamId;
result = prime * result + streamId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2PushPromiseFrame other = (DefaultHttp2PushPromiseFrame) obj;
if (headers == null) {
if (other.headers != null) {
return false;
}
} else if (!headers.equals(other.headers)) {
return false;
}
if (promisedStreamId != other.promisedStreamId) {
return false;
}
if (streamId != other.streamId) {
return false;
}
return true;
}
@Override
public String toString() {
return "DefaultHttp2PushPromiseFrame [streamId=" + streamId + ", promisedStreamId="
+ promisedStreamId + ", headers=" + headers + "]";
}
public static class Builder {
private int streamId;
private int promisedStreamId;
private Http2Headers headers;
public Builder setStreamId(int streamId) {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be > 0.");
}
this.streamId = streamId;
return this;
}
public Builder setPromisedStreamId(int promisedStreamId) {
if (promisedStreamId <= 0) {
throw new IllegalArgumentException("promisedStreamId must be > 0.");
}
this.promisedStreamId = promisedStreamId;
return this;
}
public Builder setHeaders(Http2Headers headers) {
if (headers == null) {
throw new IllegalArgumentException("headers must not be null.");
}
this.headers = headers;
return this;
}
public DefaultHttp2PushPromiseFrame build() {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be set.");
}
if (promisedStreamId <= 0) {
throw new IllegalArgumentException("promisedStreamId must be set.");
}
if (headers == null) {
throw new IllegalArgumentException("headers must be set.");
}
return new DefaultHttp2PushPromiseFrame(this);
}
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.frame;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_INT;
/**
* Default implementation of {@link Http2RstStreamFrame}.
*/
public final class DefaultHttp2RstStreamFrame implements Http2RstStreamFrame {
private final int streamId;
private final long errorCode;
private DefaultHttp2RstStreamFrame(Builder builder) {
this.streamId = builder.streamId;
this.errorCode = builder.errorCode;
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public long getErrorCode() {
return errorCode;
}
@Override
public boolean isEndOfStream() {
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (errorCode ^ (errorCode >>> 32));
result = prime * result + streamId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2RstStreamFrame other = (DefaultHttp2RstStreamFrame) obj;
if (errorCode != other.errorCode) {
return false;
}
if (streamId != other.streamId) {
return false;
}
return true;
}
/**
* Builds instances of {@link DefaultHttp2RstStreamFrame}.
*/
public static class Builder {
private int streamId;
private long errorCode = -1L;
public Builder setStreamId(int streamId) {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be > 0.");
}
this.streamId = streamId;
return this;
}
public Builder setErrorCode(long errorCode) {
if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
throw new IllegalArgumentException("Invalid errorCode value.");
}
this.errorCode = errorCode;
return this;
}
public DefaultHttp2RstStreamFrame build() {
if (streamId <= 0) {
throw new IllegalArgumentException("StreamId must be set.");
}
if (errorCode < 0L) {
throw new IllegalArgumentException("ErrorCode must be set.");
}
return new DefaultHttp2RstStreamFrame(this);
}
}
}

View File

@ -0,0 +1,164 @@
/*
* 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.frame;
/**
* Default implementation of {@link Http2SettingsFrame}.
*/
public final class DefaultHttp2SettingsFrame implements Http2SettingsFrame {
private final boolean ack;
private final Integer headerTableSize;
private final Boolean pushEnabled;
private final Long maxConcurrentStreams;
private final Integer initialWindowSize;
private DefaultHttp2SettingsFrame(Builder builder) {
this.ack = builder.ack;
this.headerTableSize = builder.headerTableSize;
this.pushEnabled = builder.pushEnabled;
this.maxConcurrentStreams = builder.maxConcurrentStreams;
this.initialWindowSize = builder.initialWindowSize;
}
@Override
public boolean isAck() {
return ack;
}
@Override
public Integer getHeaderTableSize() {
return headerTableSize;
}
@Override
public Boolean getPushEnabled() {
return pushEnabled;
}
@Override
public Long getMaxConcurrentStreams() {
return maxConcurrentStreams;
}
@Override
public Integer getInitialWindowSize() {
return initialWindowSize;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (ack ? 1231 : 1237);
result = prime * result + ((headerTableSize == null) ? 0 : headerTableSize.hashCode());
result = prime * result + ((initialWindowSize == null) ? 0 : initialWindowSize.hashCode());
result =
prime * result + ((maxConcurrentStreams == null) ? 0 : maxConcurrentStreams.hashCode());
result = prime * result + ((pushEnabled == null) ? 0 : pushEnabled.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2SettingsFrame other = (DefaultHttp2SettingsFrame) obj;
if (ack != other.ack) {
return false;
}
if (headerTableSize == null) {
if (other.headerTableSize != null) {
return false;
}
} else if (!headerTableSize.equals(other.headerTableSize)) {
return false;
}
if (initialWindowSize == null) {
if (other.initialWindowSize != null) {
return false;
}
} else if (!initialWindowSize.equals(other.initialWindowSize)) {
return false;
}
if (maxConcurrentStreams == null) {
if (other.maxConcurrentStreams != null) {
return false;
}
} else if (!maxConcurrentStreams.equals(other.maxConcurrentStreams)) {
return false;
}
if (pushEnabled == null) {
if (other.pushEnabled != null) {
return false;
}
} else if (!pushEnabled.equals(other.pushEnabled)) {
return false;
}
return true;
}
/**
* Builds instances of {@link DefaultHttp2SettingsFrame}.
*/
public static class Builder {
private boolean ack;
private Integer headerTableSize;
private Boolean pushEnabled;
private Long maxConcurrentStreams;
private Integer initialWindowSize;
public Builder setAck(boolean ack) {
this.ack = ack;
return this;
}
public Builder setHeaderTableSize(int headerTableSize) {
this.headerTableSize = headerTableSize;
return this;
}
public Builder setPushEnabled(boolean pushEnabled) {
this.pushEnabled = pushEnabled;
return this;
}
public Builder setMaxConcurrentStreams(long maxConcurrentStreams) {
this.maxConcurrentStreams = maxConcurrentStreams;
return this;
}
public Builder setInitialWindowSize(int initialWindowSize) {
this.initialWindowSize = initialWindowSize;
return this;
}
public DefaultHttp2SettingsFrame build() {
if (ack && (headerTableSize != null || pushEnabled != null || maxConcurrentStreams != null
|| initialWindowSize != null)) {
throw new IllegalArgumentException("Ack frame must not contain settings");
}
return new DefaultHttp2SettingsFrame(this);
}
}
}

View File

@ -0,0 +1,103 @@
/*
* 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.frame;
/**
* Default implementation of {@link Http2WindowUpdateFrame}.
*/
public final class DefaultHttp2WindowUpdateFrame implements Http2WindowUpdateFrame {
private final int streamId;
private final int windowSizeIncrement;
private DefaultHttp2WindowUpdateFrame(Builder builder) {
this.streamId = builder.streamId;
this.windowSizeIncrement = builder.windowSizeIncrement;
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public boolean isEndOfStream() {
return false;
}
@Override
public int getWindowSizeIncrement() {
return windowSizeIncrement;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + streamId;
result = prime * result + windowSizeIncrement;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultHttp2WindowUpdateFrame other = (DefaultHttp2WindowUpdateFrame) obj;
if (streamId != other.streamId) {
return false;
}
if (windowSizeIncrement != other.windowSizeIncrement) {
return false;
}
return true;
}
/**
* Builds instances of {@link DefaultHttp2WindowUpdateFrame}.
*/
public static class Builder {
private int streamId;
private int windowSizeIncrement;
public Builder setStreamId(int streamId) {
this.streamId = streamId;
return this;
}
public Builder setWindowSizeIncrement(int windowSizeIncrement) {
this.windowSizeIncrement = windowSizeIncrement;
return this;
}
public DefaultHttp2WindowUpdateFrame build() {
if (streamId < 0) {
throw new IllegalArgumentException("StreamId must be >= 0.");
}
if (windowSizeIncrement < 0) {
throw new IllegalArgumentException("SindowSizeIncrement must be >= 0.");
}
return new DefaultHttp2WindowUpdateFrame(this);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.frame;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
/**
* An HTTP2 data frame.
*/
public interface Http2DataFrame extends Http2StreamFrame, ByteBufHolder {
/**
* The amount of padding to follow the header data in the frame.
*/
int getPaddingLength();
/**
* Returns the data payload of this frame.
*/
@Override
ByteBuf content();
@Override
Http2DataFrame copy();
@Override
Http2DataFrame duplicate();
@Override
Http2DataFrame retain();
@Override
Http2DataFrame retain(int increment);
@Override
Http2DataFrame touch();
@Override
Http2DataFrame touch(Object hint);
}

View File

@ -0,0 +1,138 @@
/*
* 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.frame;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_ACK;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_HEADERS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_SEGMENT;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_STREAM;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_PAD_HIGH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_PAD_LOW;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_PRIORITY;
/**
* Provides utility methods for accessing specific flags as defined by the HTTP2 spec.
*/
public class Http2Flags {
private final short value;
public Http2Flags(short value) {
this.value = value;
}
/**
* Gets the underlying flags value.
*/
public short getValue() {
return value;
}
/**
* Determines whether the end-of-stream flag is set.
*/
public boolean isEndOfStream() {
return isSet(FLAG_END_STREAM);
}
/**
* Determines whether the end-of-segment flag is set.
*/
public boolean isEndOfSegment() {
return isSet(FLAG_END_SEGMENT);
}
/**
* Determines whether the end-of-headers flag is set.
*/
public boolean isEndOfHeaders() {
return isSet(FLAG_END_HEADERS);
}
/**
* Determines whether the flag is set indicating the presence of the priority field in a HEADERS
* frame.
*/
public boolean isPriorityPresent() {
return isSet(FLAG_PRIORITY);
}
/**
* Determines whether the flag is set indicating that this frame is an ACK.
*/
public boolean isAck() {
return isSet(FLAG_ACK);
}
/**
* For frames that include padding, indicates if the pad low field is present.
*/
public boolean isPadLowPresent() {
return isSet(FLAG_PAD_LOW);
}
/**
* For frames that include padding, indicates if the pad high field is present.
*/
public boolean isPadHighPresent() {
return isSet(FLAG_PAD_HIGH);
}
/**
* Indicates whether the padding flags are set properly. If pad high is set, pad low must also be
* set.
*/
public boolean isPaddingLengthValid() {
return isPadHighPresent() ? isPadLowPresent() : true;
}
/**
* Gets the number of bytes expected in the padding length field of the payload. This is
* determined by the {@link #isPadHighPresent()} and {@link #isPadLowPresent()} flags.
*/
public int getNumPaddingLengthBytes() {
return (isPadHighPresent() ? 1 : 0) + (isPadLowPresent() ? 1 : 0);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + value;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Http2Flags other = (Http2Flags) obj;
if (value != other.value) {
return false;
}
return true;
}
private boolean isSet(short mask) {
return (value & mask) != 0;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.frame;
/**
* Marker interface for all HTTP2 frame types.
*/
public interface Http2Frame {
}

View File

@ -0,0 +1,37 @@
/*
* 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.frame;
import io.netty.channel.ChannelHandlerAppender;
import io.netty.handler.codec.http2.draft10.frame.decoder.Http2FrameDecoder;
import io.netty.handler.codec.http2.draft10.frame.decoder.Http2FrameUnmarshaller;
import io.netty.handler.codec.http2.draft10.frame.encoder.Http2FrameEncoder;
import io.netty.handler.codec.http2.draft10.frame.encoder.Http2FrameMarshaller;
/**
* A combination of {@link Http2FrameEncoder} and {@link Http2FrameDecoder}.
*/
public class Http2FrameCodec extends ChannelHandlerAppender {
public Http2FrameCodec(Http2FrameMarshaller frameMarshaller,
Http2FrameUnmarshaller frameUnmarshaller) {
super(new Http2FrameEncoder(frameMarshaller), new Http2FrameDecoder(frameUnmarshaller));
}
public Http2FrameCodec() {
super(new Http2FrameEncoder(), new Http2FrameDecoder());
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.frame;
import io.netty.buffer.ByteBuf;
/**
* Constants and utility method used for encoding/decoding HTTP2 frames.
*/
public final class Http2FrameCodecUtil {
public static final int CONNECTION_STREAM_ID = 0;
public static final int DEFAULT_STREAM_PRIORITY = 0x40000000; // 2^30
public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383;
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
public static final short MAX_UNSIGNED_BYTE = 0xFF;
public static final int MAX_UNSIGNED_SHORT = 0xFFFF;
public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
public static final int FRAME_HEADER_LENGTH = 8;
public static final int FRAME_LENGTH_MASK = 0x3FFF;
public static final short FRAME_TYPE_DATA = 0x0;
public static final short FRAME_TYPE_HEADERS = 0x1;
public static final short FRAME_TYPE_PRIORITY = 0x2;
public static final short FRAME_TYPE_RST_STREAM = 0x3;
public static final short FRAME_TYPE_SETTINGS = 0x4;
public static final short FRAME_TYPE_PUSH_PROMISE = 0x5;
public static final short FRAME_TYPE_PING = 0x6;
public static final short FRAME_TYPE_GO_AWAY = 0x7;
public static final short FRAME_TYPE_WINDOW_UPDATE = 0x8;
public static final short FRAME_TYPE_CONTINUATION = 0x9;
public static final short SETTINGS_HEADER_TABLE_SIZE = 1;
public static final short SETTINGS_ENABLE_PUSH = 2;
public static final short SETTINGS_MAX_CONCURRENT_STREAMS = 3;
public static final short SETTINGS_INITIAL_WINDOW_SIZE = 4;
public static final short FLAG_END_STREAM = 0x1;
public static final short FLAG_END_SEGMENT = 0x2;
public static final short FLAG_END_HEADERS = 0x4;
public static final short FLAG_PRIORITY = 0x8;
public static final short FLAG_ACK = 0x1;
public static final short FLAG_PAD_LOW = 0x10;
public static final short FLAG_PAD_HIGH = 0x20;
/**
* Reads a big-endian (31-bit) integer from the buffer.
*/
public static int readUnsignedInt(ByteBuf buf) {
int offset = buf.readerIndex();
int value = (buf.getByte(offset + 0) & 0x7F) << 24 | (buf.getByte(offset + 1) & 0xFF) << 16
| (buf.getByte(offset + 2) & 0xFF) << 8 | buf.getByte(offset + 3) & 0xFF;
buf.skipBytes(4);
return value;
}
/**
* Writes a big-endian (32-bit) unsigned integer to the buffer.
*/
public static void writeUnsignedInt(long value, ByteBuf out) {
out.writeByte((int) ((value >> 24) & 0xFF));
out.writeByte((int) ((value >> 16) & 0xFF));
out.writeByte((int) ((value >> 8) & 0xFF));
out.writeByte((int) ((value & 0xFF)));
}
/**
* Reads the variable-length padding length field from the payload.
*/
public static int readPaddingLength(Http2Flags flags, ByteBuf payload) {
int paddingLength = 0;
if (flags.isPadHighPresent()) {
paddingLength += payload.readUnsignedByte() * 256;
}
if (flags.isPadLowPresent()) {
paddingLength += payload.readUnsignedByte();
}
return paddingLength;
}
/**
* Sets the padding flags in the given flags value as appropriate based on the padding length.
* Returns the new flags value after any padding flags have been set.
*/
public static short setPaddingFlags(short flags, int paddingLength) {
if (paddingLength > 255) {
flags |= Http2FrameCodecUtil.FLAG_PAD_HIGH;
}
if (paddingLength > 0) {
flags |= Http2FrameCodecUtil.FLAG_PAD_LOW;
}
return flags;
}
/**
* Writes the padding length field to the output buffer.
*/
public static void writePaddingLength(int paddingLength, ByteBuf out) {
if (paddingLength > 255) {
int padHigh = paddingLength / 256;
out.writeByte(padHigh);
}
if (paddingLength > 0) {
int padLow = paddingLength % 256;
out.writeByte(padLow);
}
}
private Http2FrameCodecUtil() {
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.frame;
/**
* Encapsulates the content of an HTTP2 frame header.
*/
public final class Http2FrameHeader {
private final int payloadLength;
private final int type;
private final Http2Flags flags;
private final int streamId;
private Http2FrameHeader(Builder builder) {
this.payloadLength = builder.payloadLength;
this.type = builder.type;
this.flags = builder.flags;
this.streamId = builder.streamId;
}
public int getPayloadLength() {
return payloadLength;
}
public int getType() {
return type;
}
public Http2Flags getFlags() {
return flags;
}
public int getStreamId() {
return streamId;
}
/**
* Builds instances of {@link Http2FrameHeader}.
*/
public static class Builder {
private int payloadLength;
private int type;
private Http2Flags flags = new Http2Flags((short) 0);
private int streamId;
public Builder setPayloadLength(int payloadLength) {
this.payloadLength = payloadLength;
return this;
}
public Builder setType(int type) {
this.type = type;
return this;
}
public Builder setFlags(Http2Flags flags) {
this.flags = flags;
return this;
}
public Builder setStreamId(int streamId) {
this.streamId = streamId;
return this;
}
public Http2FrameHeader build() {
return new Http2FrameHeader(this);
}
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.frame;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
/**
* An HTTP2 GO_AWAY frame indicating that the remote peer should stop creating streams for the
* connection.
*/
public interface Http2GoAwayFrame extends Http2Frame, ByteBufHolder {
/**
* The highest numbered stream identifier for which the sender of the GOAWAY frame has received
* frames on and might have taken some action on.
*/
int getLastStreamId();
/**
* The error code containing the reason for closing the connection.
*/
long getErrorCode();
/**
* Returns the debug data.
*/
@Override
ByteBuf content();
@Override
Http2GoAwayFrame copy();
@Override
Http2GoAwayFrame duplicate();
@Override
Http2GoAwayFrame retain();
@Override
Http2GoAwayFrame retain(int increment);
@Override
Http2GoAwayFrame touch();
@Override
Http2GoAwayFrame touch(Object hint);
}

View File

@ -0,0 +1,34 @@
/*
* 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.frame;
import io.netty.handler.codec.http2.draft10.Http2Headers;
/**
* The decoded form of a complete headers block for a HEADERS frame.
*/
public interface Http2HeadersFrame extends Http2StreamFrame {
/**
* Gets the priority of the stream being created.
*/
int getPriority();
/**
* Gets the decoded HTTP headers.
*/
Http2Headers getHeaders();
}

View File

@ -0,0 +1,53 @@
/*
* 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.frame;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
/**
* An HTTP2 connection PING frame.
*/
public interface Http2PingFrame extends Http2Frame, ByteBufHolder {
/**
* Indicates whether this frame is an acknowledgment of a PING sent by the peer.
*/
boolean isAck();
/**
* Returns the opaque data of this frame.
*/
@Override
ByteBuf content();
@Override
Http2PingFrame copy();
@Override
Http2PingFrame duplicate();
@Override
Http2PingFrame retain();
@Override
Http2PingFrame retain(int increment);
@Override
Http2PingFrame touch();
@Override
Http2PingFrame touch(Object hint);
}

View File

@ -0,0 +1,26 @@
/*
* 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.frame;
/**
* An HTTP2 priority frame indicating the sender-advised priority for the stream.
*/
public interface Http2PriorityFrame extends Http2StreamFrame {
/**
* The advised priority for the stream.
*/
int getPriority();
}

View File

@ -0,0 +1,34 @@
/*
* 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.frame;
import io.netty.handler.codec.http2.draft10.Http2Headers;
/**
* A decoded form of the completed headers block for a PUSH_PROMISE frame.
*/
public interface Http2PushPromiseFrame extends Http2StreamFrame {
/**
* The ID of the stream that the endpoint intends to start sending frames for.
*/
int getPromisedStreamId();
/**
* Gets the decoded HTTP headers.
*/
Http2Headers getHeaders();
}

View File

@ -0,0 +1,26 @@
/*
* 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.frame;
/**
* HTTP2 RST_STREAM frame that indicates abnormal termination of a stream.
*/
public interface Http2RstStreamFrame extends Http2StreamFrame {
/**
* The error code containing the reason for the stream being terminated.
*/
long getErrorCode();
}

View File

@ -0,0 +1,51 @@
/*
* 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.frame;
/**
* HTTP2 SETTINGS frame providing configuration parameters that affect how endpoints communicate.
*
*/
public interface Http2SettingsFrame extends Http2Frame {
/**
* Indicates whether this is an acknowledgment of the settings sent by the peer.
*/
boolean isAck();
/**
* Gets the sender's header compression table size, or {@code null} if not set.
*/
Integer getHeaderTableSize();
/**
* Gets whether or not the sender allows server push, or {@code null} if not set.
*/
Boolean getPushEnabled();
/**
* Gets the maximum number of streams the receiver is allowed to create, or {@code null} if not
* set.
*/
Long getMaxConcurrentStreams();
/**
* Gets the sender's initial flow control window in bytes, or {@code null} if not set.
*
* @return
*/
Integer getInitialWindowSize();
}

View File

@ -0,0 +1,31 @@
/*
* 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.frame;
/**
* Base interface for all frames that are associated to a stream.
*/
public interface Http2StreamFrame extends Http2Frame {
/**
* Gets the identifier of the associated stream.
*/
int getStreamId();
/**
* Indicates whether this frame represents the last frame for the stream.
*/
boolean isEndOfStream();
}

View File

@ -0,0 +1,27 @@
/*
* 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.frame;
/**
* HTTP2 WINDOW_UPDATE frame used to implement flow control.
*/
public interface Http2WindowUpdateFrame extends Http2StreamFrame {
/**
* Gets the number of bytes that the sender can transmit in addition to the existing flow control
* window.
*/
int getWindowSizeIncrement();
}

View File

@ -0,0 +1,125 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
public abstract class AbstractHeadersUnmarshaller extends AbstractHttp2FrameUnmarshaller {
/**
* A builder for a headers/push_promise frame.
*/
protected abstract class FrameBuilder {
protected ByteBuf headerBlock;
abstract int getStreamId();
final void addHeaderFragment(ByteBuf fragment, ByteBufAllocator alloc) {
if (headerBlock == null) {
headerBlock = alloc.buffer(fragment.readableBytes());
headerBlock.writeBytes(fragment);
} else {
ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + fragment.readableBytes());
buf.writeBytes(headerBlock);
buf.writeBytes(fragment);
headerBlock.release();
headerBlock = buf;
}
}
abstract Http2Frame buildFrame() throws Http2Exception;
}
private FrameBuilder frameBuilder;
@Override
protected final void validate(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameBuilder == null) {
// This frame is the beginning of a headers/push_promise.
validateStartOfHeaderBlock(frameHeader);
return;
}
// Validate the continuation of a headers block.
if (frameHeader.getType() != FRAME_TYPE_CONTINUATION) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameBuilder.getStreamId() != frameHeader.getStreamId()) {
throw protocolError("Continuation received for wrong stream. Expected %d, found %d",
frameBuilder.getStreamId(), frameHeader.getStreamId());
}
Http2Flags flags = frameHeader.getFlags();
if (!flags.isPaddingLengthValid()) {
throw protocolError("Pad high is set but pad low is not");
}
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
throw protocolError("Frame length %d to small.", frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected final Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
Http2Flags flags = header.getFlags();
if (frameBuilder == null) {
// This is the start of a headers/push_promise frame. Delegate to the subclass to create
// the appropriate builder for the frame.
frameBuilder = createFrameBuilder(header, payload, alloc);
} else {
// Processing a continuation frame for a headers/push_promise. Update the current frame
// builder with the new fragment.
int paddingLength = readPaddingLength(flags, payload);
// Determine how much data there is to read by removing the trailing
// padding.
int dataLength = payload.readableBytes() - paddingLength;
if (dataLength < 0) {
throw protocolError("Payload too small for padding.");
}
// The remainder of this frame is the headers block.
frameBuilder.addHeaderFragment(payload, alloc);
}
// If the headers are complete, build the frame.
Http2Frame frame = null;
if (flags.isEndOfHeaders()) {
frame = frameBuilder.buildFrame();
frameBuilder = null;
}
return frame;
}
protected abstract void validateStartOfHeaderBlock(Http2FrameHeader frameHeader)
throws Http2Exception;
protected abstract FrameBuilder createFrameBuilder(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception;
}

View File

@ -0,0 +1,66 @@
/*
* 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.frame.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* Abstract base class for all {@link Http2FrameUnmarshaller} classes.
*/
public abstract class AbstractHttp2FrameUnmarshaller implements Http2FrameUnmarshaller {
private Http2FrameHeader header;
@Override
public final Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
if (header == null) {
throw new IllegalArgumentException("header must be non-null.");
}
validate(header);
this.header = header;
return this;
}
@Override
public final Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
if (header == null) {
throw new IllegalStateException("header must be set before calling from().");
}
return doUnmarshall(header, payload, alloc);
}
/**
* Verifies that the given frame header is valid for the frame type(s) supported by this decoder.
*/
protected abstract void validate(Http2FrameHeader frameHeader) throws Http2Exception;
/**
* Unmarshalls the frame.
*
* @param header the frame header
* @param payload the payload of the frame.
* @param alloc an allocator for new buffers
* @return the frame
* @throws Http2Exception thrown if any protocol error was encountered.
*/
protected abstract Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception;
}

View File

@ -0,0 +1,63 @@
/*
* 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.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.Http2Error;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2Headers;
import java.io.IOException;
import com.twitter.hpack.Decoder;
import com.twitter.hpack.HeaderListener;
public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder {
private final Decoder decoder;
public DefaultHttp2HeadersDecoder() {
this.decoder = new Decoder(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE);
}
@Override
public void setHeaderTableSize(int size) throws Http2Exception {
// TODO: can we throw away the decoder and create a new one?
}
@Override
public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
try {
final Http2Headers.Builder headersBuilder = new Http2Headers.Builder();
HeaderListener listener = new HeaderListener() {
@Override
public void emitHeader(byte[] key, byte[] value) {
headersBuilder.addHeader(key, value);
}
};
decoder.decode(new ByteBufInputStream(headerBlock), listener);
decoder.endHeaderBlock(listener);
return headersBuilder.build();
} catch (IOException e) {
throw new Http2Exception(Http2Error.COMPRESSION_ERROR, e.getMessage());
}
}
}

View File

@ -0,0 +1,86 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_DATA;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* An unmarshaller for {@link Http2DataFrame} instances. The buffer contained in the frames is a
* slice of the original input buffer. If the frame needs to be persisted it should be copied.
*/
public class Http2DataFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
@Override
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_DATA) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() <= 0) {
throw protocolError("A stream ID must be > 0.");
}
Http2Flags flags = frameHeader.getFlags();
if (!flags.isPaddingLengthValid()) {
throw protocolError("Pad high is set but pad low is not");
}
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
DefaultHttp2DataFrame.Builder builder = new DefaultHttp2DataFrame.Builder();
builder.setStreamId(header.getStreamId());
Http2Flags flags = header.getFlags();
builder.setEndOfStream(flags.isEndOfStream());
// Read the padding length.
int paddingLength = readPaddingLength(flags, payload);
builder.setPaddingLength(paddingLength);
// Determine how much data there is to read by removing the trailing
// padding.
int dataLength = payload.readableBytes() - paddingLength;
if (dataLength < 0) {
throw protocolError("Frame payload too small for padding.");
}
// Copy the remaining data into the frame.
ByteBuf data = payload.slice(payload.readerIndex(), dataLength).retain();
builder.setContent(data);
// Skip the rest of the bytes in the payload.
payload.skipBytes(payload.readableBytes());
return builder.build();
}
}

View File

@ -0,0 +1,137 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_LENGTH_MASK;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
import java.util.List;
/**
* Decodes {@link Http2Frame} objects from an input {@link ByteBuf}. The frames that this handler
* emits can be configured by providing a {@link Http2FrameUnmarshaller}. By default, the
* {@link Http2StandardFrameUnmarshaller} is used to handle all frame types - see the documentation
* for details.
*
* @see Http2StandardFrameUnmarshaller
*/
public class Http2FrameDecoder extends ByteToMessageDecoder {
private enum State {
FRAME_HEADER,
FRAME_PAYLOAD,
ERROR
}
private State state;
private Http2FrameUnmarshaller frameUnmarshaller;
private int payloadLength;
public Http2FrameDecoder() {
this(new Http2StandardFrameUnmarshaller());
}
public Http2FrameDecoder(Http2FrameUnmarshaller frameUnmarshaller) {
this.frameUnmarshaller = frameUnmarshaller;
this.state = State.FRAME_HEADER;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
try {
switch (state) {
case FRAME_HEADER:
processFrameHeader(in);
if (state == State.FRAME_HEADER) {
// Still haven't read the entire frame header yet.
break;
}
// If we successfully read the entire frame header, drop down and start processing
// the payload now.
case FRAME_PAYLOAD:
processFramePayload(ctx, in, out);
break;
case ERROR:
in.skipBytes(in.readableBytes());
break;
default:
throw new IllegalStateException("Should never get here");
}
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
state = State.ERROR;
}
}
private void processFrameHeader(ByteBuf in) throws Http2Exception {
if (in.readableBytes() < FRAME_HEADER_LENGTH) {
// Wait until the entire frame header has been read.
return;
}
// Read the header and prepare the unmarshaller to read the frame.
Http2FrameHeader frameHeader = readFrameHeader(in);
payloadLength = frameHeader.getPayloadLength();
frameUnmarshaller.unmarshall(frameHeader);
// Start reading the payload for the frame.
state = State.FRAME_PAYLOAD;
}
private void processFramePayload(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Http2Exception {
if (in.readableBytes() < payloadLength) {
// Wait until the entire payload has been read.
return;
}
// Get a view of the buffer for the size of the payload.
ByteBuf payload = in.readSlice(payloadLength);
// Create the frame and add it to the output.
Http2Frame frame = frameUnmarshaller.from(payload, ctx.alloc());
if (frame != null) {
out.add(frame);
}
// Go back to reading the next frame header.
state = State.FRAME_HEADER;
}
/**
* Reads the frame header from the input buffer and creates an envelope initialized with those
* values.
*/
private static Http2FrameHeader readFrameHeader(ByteBuf in) {
int payloadLength = in.readUnsignedShort() & FRAME_LENGTH_MASK;
short type = in.readUnsignedByte();
short flags = in.readUnsignedByte();
int streamId = readUnsignedInt(in);
return new Http2FrameHeader.Builder().setPayloadLength(payloadLength).setType(type)
.setFlags(new Http2Flags(flags)).setStreamId(streamId).build();
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.frame.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* Used by the {@link Http2FrameDecoder} to unmarshall {@link Http2Frame} objects from an input
* {@link ByteBuf}.
*/
public interface Http2FrameUnmarshaller {
/**
* Prepares the unmarshaller for the next frame.
*
* @param header the header providing the detais of the frame to be unmarshalled.
* @return this unmarshaller
* @throws Http2Exception thrown if any of the information of the header violates the protocol.
*/
Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception;
/**
* Unmarshalls the frame from the payload.
*
* @param payload the payload from which the frame is to be unmarshalled.
* @param alloc the allocator for any new buffers required by the unmarshaller.
* @return the frame or {@code null} if the unmarshall operation is processing is incomplete and
* requires additional data.
* @throws Http2Exception thrown if any protocol error was encountered while unmarshalling the
* frame.
*/
Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception;
}

View File

@ -0,0 +1,68 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_GO_AWAY;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2GoAwayFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* An unmarshaller for {@link Http2GoAwayFrame} instances. The buffer contained in the frames is a
* slice of the original input buffer. If the frame needs to be persisted it should be copied.
*/
public class Http2GoAwayFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
@Override
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_GO_AWAY) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() != 0) {
throw protocolError("A stream ID must be zero.");
}
if (frameHeader.getPayloadLength() < 8) {
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
DefaultHttp2GoAwayFrame.Builder builder = new DefaultHttp2GoAwayFrame.Builder();
int lastStreamId = readUnsignedInt(payload);
builder.setLastStreamId(lastStreamId);
long errorCode = payload.readUnsignedInt();
builder.setErrorCode(errorCode);
// The remainder of this frame is the debug data.
ByteBuf data = payload.slice().retain();
builder.setDebugData(data);
return builder.build();
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.frame.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2Headers;
/**
* Decodes HPACK-encoded headers blocks into {@link Http2Headers}.
*/
public interface Http2HeadersDecoder {
/**
* Decodes the given headers block and returns the headers.
*/
Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception;
/**
* Sets the new max header table size for this decoder.
*/
void setHeaderTableSize(int size) throws Http2Exception;
}

View File

@ -0,0 +1,149 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_HEADERS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2Headers;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2HeadersFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
import com.google.common.base.Preconditions;
/**
* An unmarshaller for {@link Http2HeadersFrame} instances.
*/
public class Http2HeadersFrameUnmarshaller extends AbstractHeadersUnmarshaller {
private final Http2HeadersDecoder headersDecoder;
public Http2HeadersFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
this.headersDecoder = Preconditions.checkNotNull(headersDecoder, "headersDecoder");
}
@Override
protected void validateStartOfHeaderBlock(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_HEADERS) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() <= 0) {
throw protocolError("A stream ID must > 0.");
}
Http2Flags flags = frameHeader.getFlags();
if (flags.isPriorityPresent() && frameHeader.getPayloadLength() < 4) {
throw protocolError("Frame length too small." + frameHeader.getPayloadLength());
}
if (!flags.isPaddingLengthValid()) {
throw protocolError("Pad high is set but pad low is not");
}
if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
throw protocolError("Frame length %d too small for padding.", frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected FrameBuilder createFrameBuilder(final Http2FrameHeader header, final ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
try {
final DefaultHttp2HeadersFrame.Builder builder = new DefaultHttp2HeadersFrame.Builder();
builder.setStreamId(header.getStreamId());
Http2Flags flags = header.getFlags();
builder.setEndOfStream(flags.isEndOfStream());
// Read the padding length.
int paddingLength = readPaddingLength(flags, payload);
// Read the priority if it was included in the frame.
if (flags.isPriorityPresent()) {
int priority = readUnsignedInt(payload);
builder.setPriority(priority);
}
// Determine how much data there is to read by removing the trailing
// padding.
int dataLength = payload.readableBytes() - paddingLength;
if (dataLength < 0) {
throw protocolError("Payload too small for padding");
}
// Get a view of the header block portion of the payload.
final ByteBuf headerSlice = payload.readSlice(dataLength);
// The remainder of this frame is the headers block.
if (flags.isEndOfHeaders()) {
// Optimization: don't copy the buffer if we have the entire headers block.
return new FrameBuilder() {
@Override
int getStreamId() {
return header.getStreamId();
}
@Override
Http2Frame buildFrame() throws Http2Exception {
Http2Headers headers = headersDecoder.decodeHeaders(headerSlice);
builder.setHeaders(headers);
return builder.build();
}
};
}
// The header block is not complete. Await one or more continuation frames
// to complete the block before decoding.
FrameBuilder frameBuilder = new FrameBuilder() {
@Override
int getStreamId() {
return header.getStreamId();
}
@Override
Http2Frame buildFrame() throws Http2Exception {
try {
Http2Headers headers = headersDecoder.decodeHeaders(headerBlock);
builder.setHeaders(headers);
return builder.build();
} finally {
headerBlock.release();
headerBlock = null;
}
}
};
// Copy and add the initial fragment of the header block.
frameBuilder.addHeaderFragment(headerSlice, alloc);
return frameBuilder;
} finally {
payload.skipBytes(payload.readableBytes());
}
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PING;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2PingFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* An unmarshaller for {@link Http2PingFrame} instances. The buffer contained in the frames is a
* slice of the original input buffer. If the frame needs to be persisted it should be copied.
*/
public class Http2PingFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
@Override
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_PING) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() != 0) {
throw protocolError("A stream ID must be zero.");
}
if (frameHeader.getPayloadLength() != PING_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d incorrect size for ping.",
frameHeader.getPayloadLength());
}
}
@Override
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
DefaultHttp2PingFrame.Builder builder = new DefaultHttp2PingFrame.Builder();
builder.setAck(header.getFlags().isAck());
// The remainder of this frame is the opaque data.
ByteBuf data = payload.slice().retain();
builder.setData(data);
return builder.build();
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PRIORITY;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2PriorityFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* An unmarshaller for {@link Http2PriorityFrame} instances.
*/
public class Http2PriorityFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
@Override
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_PRIORITY) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() <= 0) {
throw protocolError("A stream ID must be > 0.");
}
if (frameHeader.getPayloadLength() < 4) {
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
DefaultHttp2PriorityFrame.Builder builder = new DefaultHttp2PriorityFrame.Builder();
builder.setStreamId(header.getStreamId());
int priority = readUnsignedInt(payload);
builder.setPriority(priority);
return builder.build();
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PUSH_PROMISE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2Headers;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2PushPromiseFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
import com.google.common.base.Preconditions;
/**
* An unmarshaller for {@link Http2PushPromiseFrame} instances.
*/
public class Http2PushPromiseFrameUnmarshaller extends AbstractHeadersUnmarshaller {
private final Http2HeadersDecoder headersDecoder;
public Http2PushPromiseFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
this.headersDecoder = Preconditions.checkNotNull(headersDecoder, "headersDecoder");
}
@Override
protected void validateStartOfHeaderBlock(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_PUSH_PROMISE) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() <= 0) {
throw protocolError("A stream ID must > 0.");
}
if (frameHeader.getPayloadLength() < 4) {
throw protocolError("Frame length too small." + frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected FrameBuilder createFrameBuilder(final Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
final DefaultHttp2PushPromiseFrame.Builder builder = new DefaultHttp2PushPromiseFrame.Builder();
builder.setStreamId(header.getStreamId());
int promisedStreamId = readUnsignedInt(payload);
builder.setPromisedStreamId(promisedStreamId);
final ByteBuf headerSlice = payload.readSlice(payload.readableBytes());
// The remainder of this frame is the headers block.
Http2Flags flags = header.getFlags();
if (flags.isEndOfHeaders()) {
// Optimization: don't copy the buffer if we have the entire headers block.
return new FrameBuilder() {
@Override
int getStreamId() {
return header.getStreamId();
}
@Override
Http2Frame buildFrame() throws Http2Exception {
Http2Headers headers = headersDecoder.decodeHeaders(headerSlice);
builder.setHeaders(headers);
return builder.build();
}
};
}
// The header block is not complete. Await one or more continuation frames
// to complete the block before decoding.
FrameBuilder frameBuilder = new FrameBuilder() {
@Override
int getStreamId() {
return header.getStreamId();
}
@Override
Http2Frame buildFrame() throws Http2Exception {
try {
Http2Headers headers = headersDecoder.decodeHeaders(headerBlock);
builder.setHeaders(headers);
return builder.build();
} finally {
headerBlock.release();
headerBlock = null;
}
}
};
// Copy and add the initial fragment of the header block.
frameBuilder.addHeaderFragment(headerSlice, alloc);
return frameBuilder;
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2RstStreamFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* An unmarshaller for {@link Http2RstStreamFrame} instances.
*/
public class Http2RstStreamFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
@Override
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_RST_STREAM) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() <= 0) {
throw protocolError("A stream ID must be > 0.");
}
if (frameHeader.getPayloadLength() < 4) {
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
DefaultHttp2RstStreamFrame.Builder builder = new DefaultHttp2RstStreamFrame.Builder();
builder.setStreamId(header.getStreamId());
long errorCode = payload.readUnsignedInt();
builder.setErrorCode(errorCode);
return builder.build();
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_SETTINGS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_ENABLE_PUSH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2SettingsFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* An unmarshaller for {@link Http2SettingsFrame} instances.
*/
public class Http2SettingsFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
@Override
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_SETTINGS) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() != 0) {
throw protocolError("A stream ID must be zero.");
}
if (frameHeader.getFlags().isAck() && frameHeader.getPayloadLength() > 0) {
throw protocolError("Ack settings frame must have an empty payload.");
}
if (frameHeader.getPayloadLength() % 5 > 0) {
throw protocolError("Frame length %d invalid.", frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
DefaultHttp2SettingsFrame.Builder builder = new DefaultHttp2SettingsFrame.Builder();
builder.setAck(header.getFlags().isAck());
int numSettings = header.getPayloadLength() / 5;
for (int index = 0; index < numSettings; ++index) {
short id = payload.readUnsignedByte();
long value = payload.readUnsignedInt();
switch (id) {
case SETTINGS_HEADER_TABLE_SIZE:
if (value <= 0L || value > Integer.MAX_VALUE) {
throw protocolError("Invalid header table size setting: %d", value);
}
builder.setHeaderTableSize((int) value);
break;
case SETTINGS_ENABLE_PUSH:
if (value != 0L && value != 1L) {
throw protocolError("Invalid enable push setting: %d", value);
}
builder.setPushEnabled(value == 1);
break;
case SETTINGS_MAX_CONCURRENT_STREAMS:
if (value < 0L) {
throw protocolError("Invalid max concurrent streams setting: %d", value);
}
builder.setMaxConcurrentStreams(value);
break;
case SETTINGS_INITIAL_WINDOW_SIZE:
if (value < 0L || value > Integer.MAX_VALUE) {
throw protocolError("Invalid initial window size setting: %d", value);
}
builder.setInitialWindowSize((int) value);
break;
default:
throw protocolError("Unsupported setting: %d", id);
}
}
return builder.build();
}
}

View File

@ -0,0 +1,115 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_DATA;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_GO_AWAY;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_HEADERS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PING;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PRIORITY;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PUSH_PROMISE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_SETTINGS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
/**
* A composite {@link Http2FrameUnmarshaller} that supports all frames identified by the HTTP2 spec.
* This unmarshalls the following frames:
* <p>
* {@link Http2DataFrame} (buffer is a slice of input buffer - must be copied if persisted)<br>
* {@link Http2GoAwayFrame} (buffer is a slice of input buffer - must be copied if persisted)<br>
* {@link Http2HeadersFrame}<br>
* {@link Http2PingFrame} (buffer is a slice of input buffer - must be copied if persisted)<br>
* {@link Http2PriorityFrame}<br>
* {@link Http2PushPromiseFrame}<br>
* {@link Http2RstStreamFrame}<br>
* {@link Http2SettingsFrame}<br>
* {@link Http2WindowUpdateFrame}<br>
*/
public class Http2StandardFrameUnmarshaller implements Http2FrameUnmarshaller {
private final Http2FrameUnmarshaller[] unmarshallers;
private Http2FrameUnmarshaller activeUnmarshaller;
public Http2StandardFrameUnmarshaller() {
this(new DefaultHttp2HeadersDecoder());
}
public Http2StandardFrameUnmarshaller(Http2HeadersDecoder headersDecoder) {
unmarshallers = new Http2FrameUnmarshaller[FRAME_TYPE_CONTINUATION + 1];
unmarshallers[FRAME_TYPE_DATA] = new Http2DataFrameUnmarshaller();
unmarshallers[FRAME_TYPE_HEADERS] = new Http2HeadersFrameUnmarshaller(headersDecoder);
unmarshallers[FRAME_TYPE_PRIORITY] = new Http2PriorityFrameUnmarshaller();
unmarshallers[FRAME_TYPE_RST_STREAM] = new Http2RstStreamFrameUnmarshaller();
unmarshallers[FRAME_TYPE_SETTINGS] = new Http2SettingsFrameUnmarshaller();
unmarshallers[FRAME_TYPE_PUSH_PROMISE] = new Http2PushPromiseFrameUnmarshaller(headersDecoder);
unmarshallers[FRAME_TYPE_PING] = new Http2PingFrameUnmarshaller();
unmarshallers[FRAME_TYPE_GO_AWAY] = new Http2GoAwayFrameUnmarshaller();
unmarshallers[FRAME_TYPE_WINDOW_UPDATE] = new Http2WindowUpdateFrameUnmarshaller();
unmarshallers[FRAME_TYPE_CONTINUATION] = new Http2FrameUnmarshaller() {
private String msg = "Received continuation without headers or push_promise";
@Override
public Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
throw protocolError(msg);
}
@Override
public Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
throw protocolError(msg);
}
};
}
@Override
public Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
// If we're not in the middle of unmarshalling a continued frame (e.g. headers,
// push_promise), select the appropriate marshaller for the frame type.
if (activeUnmarshaller == null) {
int type = header.getType();
if (type < 0 || type >= unmarshallers.length || unmarshallers[type] == null) {
throw protocolError("Unsupported frame type: %d", type);
}
activeUnmarshaller = unmarshallers[type];
}
// Prepare the unmarshaller.
activeUnmarshaller.unmarshall(header);
return this;
}
@Override
public Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
if (activeUnmarshaller == null) {
throw new IllegalStateException("Must call unmarshall() before calling from().");
}
Http2Frame frame = activeUnmarshaller.from(payload, alloc);
if (frame != null) {
// The unmarshall is complete and does not require more frames. Clear the active
// marshaller so that we select a fresh marshaller next time.
activeUnmarshaller = null;
}
return frame;
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.frame.decoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
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.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
public class Http2WindowUpdateFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
@Override
protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
if (frameHeader.getType() != FRAME_TYPE_WINDOW_UPDATE) {
throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
}
if (frameHeader.getStreamId() < 0) {
throw protocolError("Stream Id must be >=0: ", frameHeader.getStreamId());
}
if (frameHeader.getPayloadLength() < 4) {
throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
}
if (frameHeader.getPayloadLength() > PING_FRAME_PAYLOAD_LENGTH) {
throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
}
}
@Override
protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
ByteBufAllocator alloc) throws Http2Exception {
DefaultHttp2WindowUpdateFrame.Builder builder = new DefaultHttp2WindowUpdateFrame.Builder();
builder.setStreamId(header.getStreamId());
int windowSizeIncrement = readUnsignedInt(payload);
builder.setWindowSizeIncrement(windowSizeIncrement);
return builder.build();
}
}

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Decoder and related classes for HTTP2 frames.
*/
package io.netty.handler.codec.http2.draft10.frame.decoder;

View File

@ -0,0 +1,65 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
/**
* Abstract base class for all {@link Http2FrameMarshaller}s.
*/
public abstract class AbstractHttp2FrameMarshaller<T extends Http2Frame> implements
Http2FrameMarshaller {
private final Class<T> frameType;
protected AbstractHttp2FrameMarshaller(Class<T> frameType) {
if (frameType == null) {
throw new IllegalArgumentException("frameType must be non-null.");
}
this.frameType = frameType;
}
@Override
public final void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc)
throws Http2Exception {
if (frame == null) {
throw new IllegalArgumentException("frame must be non-null.");
}
if (!frameType.isAssignableFrom(frame.getClass())) {
throw protocolError("Unsupported frame type: %s", frame.getClass().getName());
}
@SuppressWarnings("unchecked")
T frameT = (T) frame;
doMarshall(frameT, out, alloc);
}
/**
* Marshals the frame to the output buffer.
*
* @param frame the frame to be marshalled
* @param out the buffer to marshall the frame to.
* @param alloc an allocator that this marshaller may use for creating intermediate buffers as
* needed.
*/
protected abstract void doMarshall(T frame, ByteBuf out, ByteBufAllocator alloc)
throws Http2Exception;
}

View File

@ -0,0 +1,63 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_HEADER_TABLE_SIZE;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
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 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;
public DefaultHttp2HeadersEncoder() {
this.encoder = new Encoder(DEFAULT_HEADER_TABLE_SIZE);
}
@Override
public void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception {
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);
encoder.encodeHeader(stream, key, value);
}
encoder.endHeaders(stream);
} catch (IOException e) {
throw Http2Exception.format(Http2Error.COMPRESSION_ERROR, "Failed encoding headers block: %s",
e.getMessage());
}
}
@Override
public void setHeaderTableSize(int size) throws Http2Exception {
// TODO: can we throw away the encoder and create a new one?
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_STREAM;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_DATA;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.setPaddingFlags;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writePaddingLength;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
public class Http2DataFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2DataFrame> {
public Http2DataFrameMarshaller() {
super(Http2DataFrame.class);
}
@Override
protected void doMarshall(Http2DataFrame frame, ByteBuf out, ByteBufAllocator alloc) {
ByteBuf data = frame.content();
Http2Flags flags = getFlags(frame);
// Write the frame header.
int payloadLength = data.readableBytes() + frame.getPaddingLength()
+ (flags.isPadHighPresent() ? 1 : 0) + (flags.isPadLowPresent() ? 1 : 0);
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(FRAME_TYPE_DATA);
out.writeByte(flags.getValue());
out.writeInt(frame.getStreamId());
writePaddingLength(frame.getPaddingLength(), out);
// Write the data.
out.writeBytes(data, data.readerIndex(), data.readableBytes());
// Write the required padding.
out.writeZero(frame.getPaddingLength());
}
private Http2Flags getFlags(Http2DataFrame frame) {
short flags = 0;
if (frame.isEndOfStream()) {
flags |= FLAG_END_STREAM;
}
flags = setPaddingFlags(flags, frame.getPaddingLength());
return new Http2Flags(flags);
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.frame.encoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
/**
* Encodes {@link Http2Frame} objects and writes them to an output {@link ByteBuf}. The set of frame
* types that is handled by this encoder is given by the {@link Http2FrameMarshaller}. By default,
* the {@link Http2StandardFrameMarshaller} is used.
*
* @see Http2StandardFrameMarshaller
*/
public class Http2FrameEncoder extends MessageToByteEncoder<Http2Frame> {
private final Http2FrameMarshaller frameMarshaller;
public Http2FrameEncoder() {
this(new Http2StandardFrameMarshaller());
}
public Http2FrameEncoder(Http2FrameMarshaller frameMarshaller) {
this.frameMarshaller = frameMarshaller;
}
@Override
protected void encode(ChannelHandlerContext ctx, Http2Frame frame, ByteBuf out) throws Exception {
try {
frameMarshaller.marshall(frame, out, ctx.alloc());
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.frame.encoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
/**
* Marshalls {@link Http2Frame} objects to a {@link ByteBuf}.
*/
public interface Http2FrameMarshaller {
/**
* Marshalls the given frame to the output buffer.
*
* @param frame the frame to be marshalled.
* @param out the buffer to marshall the frame to.
* @param alloc an allocator that this marshaller may use for creating intermediate buffers as
* needed.
* @throws Http2Exception thrown if the given fram is not supported by this marshaller.
*/
void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc) throws Http2Exception;
}

View File

@ -0,0 +1,49 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_GO_AWAY;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.frame.Http2GoAwayFrame;
public class Http2GoAwayFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2GoAwayFrame> {
public Http2GoAwayFrameMarshaller() {
super(Http2GoAwayFrame.class);
}
@Override
protected void doMarshall(Http2GoAwayFrame frame, ByteBuf out, ByteBufAllocator alloc) {
ByteBuf data = frame.content();
// Write the frame header.
int payloadLength = data.readableBytes() + 8;
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(FRAME_TYPE_GO_AWAY);
out.writeByte(0);
out.writeInt(0);
out.writeInt(frame.getLastStreamId());
writeUnsignedInt(frame.getErrorCode(), out);
// Write the debug data.
out.writeBytes(data, data.readerIndex(), data.readableBytes());
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.frame.encoder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.Http2Headers;
/**
* Encodes {@link Http2Headers} into HPACK-encoded headers blocks.
*/
public interface Http2HeadersEncoder {
/**
* Encodes the given headers and writes the output headers block to the given output buffer.
*
* @param headers the headers to be encoded.
* @param buffer the buffer to write the headers to.
*/
void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception;
/**
* Updates the maximum header table size for this encoder.
*/
void setHeaderTableSize(int size) throws Http2Exception;
}

View File

@ -0,0 +1,116 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_HEADERS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_STREAM;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_PRIORITY;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_HEADERS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import io.netty.buffer.ByteBuf;
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");
}
@Override
protected void doMarshall(Http2HeadersFrame frame, ByteBuf out, ByteBufAllocator alloc)
throws Http2Exception {
// TODO(nathanmittler): include padding?
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH;
boolean hasPriority = frame.getPriority() != DEFAULT_STREAM_PRIORITY;
if (hasPriority) {
// The first frame will include the priority.
maxFragmentLength -= 4;
}
// Encode the entire header block into an intermediate buffer.
ByteBuf headerBlock = alloc.buffer();
headersEncoder.encodeHeaders(frame.getHeaders(), headerBlock);
ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
int payloadLength = fragment.readableBytes() + (hasPriority ? 4 : 0);
boolean endOfHeaders = headerBlock.readableBytes() == 0;
// Get the flags for the frame.
short flags = 0;
if (endOfHeaders) {
flags |= FLAG_END_HEADERS;
}
if (frame.isEndOfStream()) {
flags |= FLAG_END_STREAM;
}
if (hasPriority) {
flags |= FLAG_PRIORITY;
}
// Write the frame header.
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(FRAME_TYPE_HEADERS);
out.writeByte(flags);
out.writeInt(frame.getStreamId());
// Write out the priority if it's present.
if (hasPriority) {
out.writeInt(frame.getPriority());
}
// Write the first fragment.
out.writeBytes(fragment);
// Process any continuation frames there might be.
while (headerBlock.readableBytes() > 0) {
writeContinuationFrame(frame.getStreamId(), headerBlock, out);
}
// Release the intermediate buffer.
headerBlock.release();
}
/**
* Writes a single continuation frame with a fragment of the header block to the output buffer.
*/
private void writeContinuationFrame(int streamId, ByteBuf headerBlock, ByteBuf out) {
ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), MAX_FRAME_PAYLOAD_LENGTH));
// Write the frame header.
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
out.writeShort(fragment.readableBytes());
out.writeByte(FRAME_TYPE_CONTINUATION);
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
out.writeInt(streamId);
// Write the headers block.
out.writeBytes(fragment);
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_ACK;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PING;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.frame.Http2PingFrame;
public class Http2PingFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2PingFrame> {
public Http2PingFrameMarshaller() {
super(Http2PingFrame.class);
}
@Override
protected void doMarshall(Http2PingFrame frame, ByteBuf out, ByteBufAllocator alloc) {
ByteBuf data = frame.content();
// Write the frame header.
int payloadLength = data.readableBytes();
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(FRAME_TYPE_PING);
out.writeByte(frame.isAck() ? FLAG_ACK : 0);
out.writeInt(0);
// Write the debug data.
out.writeBytes(data, data.readerIndex(), data.readableBytes());
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PRIORITY;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.frame.Http2PriorityFrame;
public class Http2PriorityFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2PriorityFrame> {
public Http2PriorityFrameMarshaller() {
super(Http2PriorityFrame.class);
}
@Override
protected void doMarshall(Http2PriorityFrame frame, ByteBuf out, ByteBufAllocator alloc) {
// Write the frame header.
int payloadLength = 4;
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(FRAME_TYPE_PRIORITY);
out.writeByte(0);
out.writeInt(frame.getStreamId());
// Write out the priority if it's present.
out.writeInt(frame.getPriority());
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_HEADERS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_PUSH_PROMISE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
import io.netty.buffer.ByteBuf;
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> {
private final Http2HeadersEncoder headersEncoder;
public Http2PushPromiseFrameMarshaller(Http2HeadersEncoder headersEncoder) {
super(Http2PushPromiseFrame.class);
this.headersEncoder = Preconditions.checkNotNull(headersEncoder, "headersEncoder");
}
@Override
protected void doMarshall(Http2PushPromiseFrame frame, ByteBuf out, ByteBufAllocator alloc)
throws Http2Exception {
// Max size minus the promised stream ID.
int maxFragmentLength = MAX_FRAME_PAYLOAD_LENGTH - 4;
// Encode the entire header block into an intermediate buffer.
ByteBuf headerBlock = alloc.buffer();
headersEncoder.encodeHeaders(frame.getHeaders(), headerBlock);
ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), maxFragmentLength));
// Write the frame header.
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
out.writeShort(fragment.readableBytes() + 4);
out.writeByte(FRAME_TYPE_PUSH_PROMISE);
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
out.writeInt(frame.getStreamId());
// Write out the promised stream ID.
out.writeInt(frame.getPromisedStreamId());
// Write the first fragment.
out.writeBytes(fragment);
// Process any continuation frames there might be.
while (headerBlock.readableBytes() > 0) {
writeContinuationFrame(frame.getStreamId(), headerBlock, out);
}
// Release the intermediate buffer.
headerBlock.release();
}
/**
* Writes a single continuation frame with a fragment of the header block to the output buffer.
*/
private void writeContinuationFrame(int streamId, ByteBuf headerBlock, ByteBuf out) {
ByteBuf fragment =
headerBlock.readSlice(Math.min(headerBlock.readableBytes(), MAX_FRAME_PAYLOAD_LENGTH));
// Write the frame header.
out.ensureWritable(FRAME_HEADER_LENGTH + fragment.readableBytes());
out.writeShort(fragment.readableBytes());
out.writeByte(FRAME_TYPE_CONTINUATION);
out.writeByte(headerBlock.readableBytes() == 0 ? FLAG_END_HEADERS : 0);
out.writeInt(streamId);
// Write the headers block.
out.writeBytes(fragment);
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_RST_STREAM;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.frame.Http2RstStreamFrame;
public class Http2RstStreamFrameMarshaller extends
AbstractHttp2FrameMarshaller<Http2RstStreamFrame> {
public Http2RstStreamFrameMarshaller() {
super(Http2RstStreamFrame.class);
}
@Override
protected void doMarshall(Http2RstStreamFrame frame, ByteBuf out, ByteBufAllocator alloc) {
// Write the frame header.
int payloadLength = 4;
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(FRAME_TYPE_RST_STREAM);
out.writeByte(0);
out.writeInt(frame.getStreamId());
writeUnsignedInt(frame.getErrorCode(), out);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_SETTINGS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_ENABLE_PUSH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.SETTINGS_MAX_CONCURRENT_STREAMS;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.writeUnsignedInt;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil;
import io.netty.handler.codec.http2.draft10.frame.Http2SettingsFrame;
public class Http2SettingsFrameMarshaller extends AbstractHttp2FrameMarshaller<Http2SettingsFrame> {
public Http2SettingsFrameMarshaller() {
super(Http2SettingsFrame.class);
}
@Override
protected void doMarshall(Http2SettingsFrame frame, ByteBuf out, ByteBufAllocator alloc) {
int numSettings = 0;
numSettings += frame.getPushEnabled() != null ? 1 : 0;
numSettings += frame.getHeaderTableSize() != null ? 1 : 0;
numSettings += frame.getInitialWindowSize() != null ? 1 : 0;
numSettings += frame.getMaxConcurrentStreams() != null ? 1 : 0;
// Write the frame header.
int payloadLength = 5 * numSettings;
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(FRAME_TYPE_SETTINGS);
out.writeByte(frame.isAck() ? Http2FrameCodecUtil.FLAG_ACK : 0);
out.writeInt(0);
if (frame.getPushEnabled() != null) {
out.writeByte(SETTINGS_ENABLE_PUSH);
writeUnsignedInt(frame.getPushEnabled() ? 1L : 0L, out);
}
if (frame.getHeaderTableSize() != null) {
out.writeByte(SETTINGS_HEADER_TABLE_SIZE);
writeUnsignedInt(frame.getHeaderTableSize(), out);
}
if (frame.getInitialWindowSize() != null) {
out.writeByte(SETTINGS_INITIAL_WINDOW_SIZE);
writeUnsignedInt(frame.getInitialWindowSize(), out);
}
if (frame.getMaxConcurrentStreams() != null) {
out.writeByte(SETTINGS_MAX_CONCURRENT_STREAMS);
writeUnsignedInt(frame.getMaxConcurrentStreams(), out);
}
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.Http2Exception;
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2HeadersFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2PushPromiseFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
import io.netty.handler.codec.http2.draft10.frame.Http2GoAwayFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2PingFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2PriorityFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2RstStreamFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2SettingsFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
/**
* A composite {@link Http2FrameMarshaller} that supports all frames identified by the HTTP2 spec.
* This handles marshalling for the following frame types:
* <p>
* {@link Http2DataFrame} <br>
* {@link Http2GoAwayFrame} <br>
* {@link Http2HeadersFrame} <br>
* {@link Http2PingFrame} <br>
* {@link Http2PriorityFrame} <br>
* {@link Http2PushPromiseFrame} <br>
* {@link Http2RstStreamFrame} <br>
* {@link Http2SettingsFrame} <br>
* {@link Http2WindowUpdateFrame} <br>
*/
public class Http2StandardFrameMarshaller implements Http2FrameMarshaller {
private final Http2FrameMarshaller dataMarshaller;
private final Http2FrameMarshaller headersMarshaller;
private final Http2FrameMarshaller goAwayMarshaller;
private final Http2FrameMarshaller pingMarshaller;
private final Http2FrameMarshaller priorityMarshaller;
private final Http2FrameMarshaller pushPromiseMarshaller;
private final Http2FrameMarshaller rstStreamMarshaller;
private final Http2FrameMarshaller settingsMarshaller;
private final Http2FrameMarshaller windowUpdateMarshaller;
public Http2StandardFrameMarshaller() {
this(new DefaultHttp2HeadersEncoder());
}
public Http2StandardFrameMarshaller(Http2HeadersEncoder headersEncoder) {
dataMarshaller = new Http2DataFrameMarshaller();
headersMarshaller = new Http2HeadersFrameMarshaller(headersEncoder);
goAwayMarshaller = new Http2GoAwayFrameMarshaller();
pingMarshaller = new Http2PingFrameMarshaller();
priorityMarshaller = new Http2PriorityFrameMarshaller();
pushPromiseMarshaller = new Http2PushPromiseFrameMarshaller(headersEncoder);
rstStreamMarshaller = new Http2RstStreamFrameMarshaller();
settingsMarshaller = new Http2SettingsFrameMarshaller();
windowUpdateMarshaller = new Http2WindowUpdateFrameMarshaller();
}
@Override
public void marshall(Http2Frame frame, ByteBuf out, ByteBufAllocator alloc)
throws Http2Exception {
Http2FrameMarshaller marshaller = null;
if (frame == null) {
throw new IllegalArgumentException("frame must be non-null");
}
if (frame instanceof Http2DataFrame) {
marshaller = dataMarshaller;
} else if (frame instanceof Http2HeadersFrame) {
marshaller = headersMarshaller;
} else if (frame instanceof Http2GoAwayFrame) {
marshaller = goAwayMarshaller;
} else if (frame instanceof Http2PingFrame) {
marshaller = pingMarshaller;
} else if (frame instanceof Http2PriorityFrame) {
marshaller = priorityMarshaller;
} else if (frame instanceof Http2PushPromiseFrame) {
marshaller = pushPromiseMarshaller;
} else if (frame instanceof Http2RstStreamFrame) {
marshaller = rstStreamMarshaller;
} else if (frame instanceof Http2SettingsFrame) {
marshaller = settingsMarshaller;
} else if (frame instanceof Http2WindowUpdateFrame) {
marshaller = windowUpdateMarshaller;
}
if (marshaller == null) {
throw protocolError("Unsupported frame type: %s", frame.getClass().getName());
}
marshaller.marshall(frame, out, alloc);
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.frame.encoder;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_WINDOW_UPDATE;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
public class Http2WindowUpdateFrameMarshaller extends
AbstractHttp2FrameMarshaller<Http2WindowUpdateFrame> {
public Http2WindowUpdateFrameMarshaller() {
super(Http2WindowUpdateFrame.class);
}
@Override
protected void doMarshall(Http2WindowUpdateFrame frame, ByteBuf out, ByteBufAllocator alloc) {
// Write the frame header.
int payloadLength = 4;
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
out.writeShort(payloadLength);
out.writeByte(FRAME_TYPE_WINDOW_UPDATE);
out.writeByte(0);
out.writeInt(frame.getStreamId());
out.writeInt(frame.getWindowSizeIncrement());
}
}

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Encoder and related classes for HTTP2.
*/
package io.netty.handler.codec.http2.draft10.frame.encoder;

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Encoder, decoder and their related message types for HTTP2 frames.
*/
package io.netty.handler.codec.http2.draft10.frame;

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Encoder, decoder and their related message types for HTTP2.
*/
package io.netty.handler.codec.http2.draft10;

View File

@ -0,0 +1,308 @@
/*
* 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.connection;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
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 io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
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.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Tests for {@link DefaultHttp2Connection}.
*/
public class DefaultHttp2ConnectionTest {
@Mock
private Listener listener;
@Mock
private ChannelHandlerContext ctx;
@Mock
private ChannelFuture future;
@Mock
private ChannelPromise promise;
private DefaultHttp2Connection server;
private DefaultHttp2Connection client;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(ctx.writeAndFlush(any())).thenReturn(future);
when(ctx.newSucceededFuture()).thenReturn(future);
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
ChannelFutureListener listener =
(ChannelFutureListener) invocation.getArguments()[0];
listener.operationComplete(future);
return null;
}
}).when(future).addListener(any(ChannelFutureListener.class));
server = new DefaultHttp2Connection(true);
client = new DefaultHttp2Connection(false);
server.addListener(listener);
client.addListener(listener);
}
@Test(expected = Http2Exception.class)
public void getStreamOrFailWithoutStreamShouldFail() throws Http2Exception {
server.getStreamOrFail(100);
}
@Test
public void getStreamWithoutStreamShouldReturnNull() {
assertNull(server.getStream(100));
}
@Test
public void serverCreateStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = server.local().createStream(2, 1, false);
assertEquals(2, stream.getId());
assertEquals(1, stream.getPriority());
assertEquals(State.OPEN, stream.getState());
assertEquals(1, server.getActiveStreams().size());
assertEquals(2, server.local().getLastStreamCreated());
verify(listener).streamCreated(2);
stream = server.local().createStream(4, 256, true);
assertEquals(4, stream.getId());
assertEquals(256, stream.getPriority());
assertEquals(State.HALF_CLOSED_LOCAL, stream.getState());
assertEquals(2, server.getActiveStreams().size());
assertEquals(4, server.local().getLastStreamCreated());
verify(listener).streamCreated(4);
stream = server.remote().createStream(3, Integer.MAX_VALUE, true);
assertEquals(3, stream.getId());
assertEquals(Integer.MAX_VALUE, stream.getPriority());
assertEquals(State.HALF_CLOSED_REMOTE, stream.getState());
assertEquals(3, server.getActiveStreams().size());
assertEquals(3, server.remote().getLastStreamCreated());
verify(listener).streamCreated(3);
stream = server.remote().createStream(5, 1, false);
assertEquals(5, stream.getId());
assertEquals(1, stream.getPriority());
assertEquals(State.OPEN, stream.getState());
assertEquals(4, server.getActiveStreams().size());
assertEquals(5, server.remote().getLastStreamCreated());
verify(listener).streamCreated(5);
}
@Test
public void clientCreateStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = client.remote().createStream(2, 1, false);
assertEquals(2, stream.getId());
assertEquals(1, stream.getPriority());
assertEquals(State.OPEN, stream.getState());
assertEquals(1, client.getActiveStreams().size());
assertEquals(2, client.remote().getLastStreamCreated());
verify(listener).streamCreated(2);
stream = client.remote().createStream(4, 256, true);
assertEquals(4, stream.getId());
assertEquals(256, stream.getPriority());
assertEquals(State.HALF_CLOSED_REMOTE, stream.getState());
assertEquals(2, client.getActiveStreams().size());
assertEquals(4, client.remote().getLastStreamCreated());
verify(listener).streamCreated(4);
stream = client.local().createStream(3, Integer.MAX_VALUE, true);
assertEquals(3, stream.getId());
assertEquals(Integer.MAX_VALUE, stream.getPriority());
assertEquals(State.HALF_CLOSED_LOCAL, stream.getState());
assertEquals(3, client.getActiveStreams().size());
assertEquals(3, client.local().getLastStreamCreated());
verify(listener).streamCreated(3);
stream = client.local().createStream(5, 1, false);
assertEquals(5, stream.getId());
assertEquals(1, stream.getPriority());
assertEquals(State.OPEN, stream.getState());
assertEquals(4, client.getActiveStreams().size());
assertEquals(5, client.local().getLastStreamCreated());
verify(listener).streamCreated(5);
}
@Test
public void serverReservePushStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, 1, true);
Http2Stream pushStream = server.local().reservePushStream(2, stream);
assertEquals(2, pushStream.getId());
assertEquals(2, pushStream.getPriority());
assertEquals(State.RESERVED_LOCAL, pushStream.getState());
assertEquals(1, server.getActiveStreams().size());
assertEquals(2, server.local().getLastStreamCreated());
verify(listener).streamCreated(3);
verify(listener).streamCreated(2);
}
@Test
public void clientReservePushStreamShouldSucceed() throws Http2Exception {
Http2Stream stream = client.remote().createStream(2, 1, true);
Http2Stream pushStream = client.local().reservePushStream(3, stream);
assertEquals(3, pushStream.getId());
assertEquals(2, pushStream.getPriority());
assertEquals(State.RESERVED_LOCAL, pushStream.getState());
assertEquals(1, client.getActiveStreams().size());
assertEquals(3, client.local().getLastStreamCreated());
verify(listener).streamCreated(2);
verify(listener).streamCreated(3);
}
@Test(expected = Http2Exception.class)
public void createStreamWithInvalidIdShouldThrow() throws Http2Exception {
server.remote().createStream(1, 1, true);
}
@Test(expected = Http2Exception.class)
public void maxAllowedStreamsExceededShouldThrow() throws Http2Exception {
server.local().setMaxStreams(0);
server.local().createStream(2, 1, true);
}
@Test(expected = Http2Exception.class)
public void invalidPriorityShouldThrow() throws Http2Exception {
server.local().createStream(2, -1, true);
}
@Test(expected = Http2Exception.class)
public void reserveWithPushDisallowedShouldThrow() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, 1, true);
server.remote().setPushToAllowed(false);
server.local().reservePushStream(2, stream);
}
@Test(expected = Http2Exception.class)
public void goAwayReceivedShouldDisallowCreation() throws Http2Exception {
server.goAwayReceived();
server.remote().createStream(3, 1, true);
}
@Test
public void activeStreamsShouldBeSortedByPriority() throws Http2Exception {
server.local().createStream(2, 1, false);
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();
assertEquals(2, activeStreams.get(0).getId());
assertEquals(5, activeStreams.get(1).getId());
assertEquals(4, activeStreams.get(2).getId());
assertEquals(3, activeStreams.get(3).getId());
}
@Test
public void priorityChangeShouldReorderActiveStreams() throws Http2Exception {
server.local().createStream(2, 1, false);
server.local().createStream(4, 256, true);
server.remote().createStream(3, Integer.MAX_VALUE, true);
server.remote().createStream(5, 1, false);
Http2Stream stream7 = server.remote().createStream(7, 1, false);
server.remote().createStream(9, 1, false);
// Make this this highest priority.
stream7.setPriority(0);
List<Http2Stream> activeStreams = server.getActiveStreams();
assertEquals(7, activeStreams.get(0).getId());
assertEquals(2, activeStreams.get(1).getId());
assertEquals(5, activeStreams.get(2).getId());
assertEquals(9, activeStreams.get(3).getId());
assertEquals(4, activeStreams.get(4).getId());
assertEquals(3, activeStreams.get(5).getId());
}
@Test
public void closeShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, 1, true);
stream.close(ctx, future);
assertEquals(State.CLOSED, stream.getState());
assertTrue(server.getActiveStreams().isEmpty());
verify(listener).streamClosed(3);
}
@Test
public void closeLocalWhenOpenShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, 1, false);
stream.closeLocalSide(ctx, future);
assertEquals(State.HALF_CLOSED_LOCAL, stream.getState());
assertEquals(1, server.getActiveStreams().size());
verify(listener, never()).streamClosed(3);
}
@Test
public void closeRemoteWhenOpenShouldSucceed() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, 1, false);
stream.closeRemoteSide(ctx, future);
assertEquals(State.HALF_CLOSED_REMOTE, stream.getState());
assertEquals(1, server.getActiveStreams().size());
verify(listener, never()).streamClosed(3);
}
@Test
public void closeOnlyOpenSideShouldClose() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, 1, true);
stream.closeLocalSide(ctx, future);
assertEquals(State.CLOSED, stream.getState());
assertTrue(server.getActiveStreams().isEmpty());
verify(listener).streamClosed(3);
}
@Test
public void sendGoAwayShouldCloseConnection() {
server.sendGoAway(ctx, promise, null);
verify(ctx).close(promise);
}
@Test
public void sendGoAwayShouldCloseAfterConnectionInactive() throws Http2Exception {
Http2Stream stream = server.remote().createStream(3, 1, true);
server.sendGoAway(ctx, promise, null);
verify(ctx, never()).close(promise);
// Now close the stream and verify that the context was closed too.
stream.close(ctx, future);
verify(ctx).close(promise);
}
}

View File

@ -0,0 +1,158 @@
/*
* 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.connection;
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 static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
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 io.netty.buffer.ByteBuf;
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.InboundFlowController.FrameWriter;
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 org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Tests for {@link DefaultInboundFlowController}.
*/
public class DefaultInboundFlowControllerTest {
private static final int STREAM_ID = 1;
private DefaultInboundFlowController controller;
@Mock
private Http2Connection connection;
@Mock
private ByteBuf buffer;
@Mock
private FrameWriter frameWriter;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
// Mock the creation of a single stream.
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Listener listener = (Listener) invocation.getArguments()[0];
listener.streamCreated(STREAM_ID);
return null;
}
}).when(connection).addListener(any(Listener.class));
controller = new DefaultInboundFlowController(connection);
}
@Test
public void dataFrameShouldBeAccepted() throws Http2Exception {
Http2DataFrame frame = mockDataFrame(10, false);
controller.applyInboundFlowControl(frame, frameWriter);
verifyWindowUpdateNotSent();
}
@Test(expected = Http2Exception.class)
public void connectionFlowControlExceededShouldThrow() throws Http2Exception {
Http2DataFrame frame = mockDataFrame(DEFAULT_FLOW_CONTROL_WINDOW_SIZE + 1, true);
controller.applyInboundFlowControl(frame, frameWriter);
}
@Test
public void halfWindowRemainingShouldUpdateConnectionWindow() throws Http2Exception {
int dataSize = (DEFAULT_FLOW_CONTROL_WINDOW_SIZE / 2) + 1;
int newWindow = DEFAULT_FLOW_CONTROL_WINDOW_SIZE - dataSize;
int windowDelta = DEFAULT_FLOW_CONTROL_WINDOW_SIZE - newWindow;
// Set end-of-stream on the frame, so no window update will be sent for the stream.
Http2DataFrame frame = mockDataFrame(dataSize, true);
controller.applyInboundFlowControl(frame, frameWriter);
verify(frameWriter).writeFrame(eq(windowUpdate(CONNECTION_STREAM_ID, windowDelta)));
}
@Test
public void halfWindowRemainingShouldUpdateAllWindows() throws Http2Exception {
int dataSize = (DEFAULT_FLOW_CONTROL_WINDOW_SIZE / 2) + 1;
int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
int windowDelta = getWindowDelta(initialWindowSize, initialWindowSize, dataSize);
// Don't set end-of-stream so we'll get a window update for the stream as well.
Http2DataFrame frame = mockDataFrame(dataSize, false);
controller.applyInboundFlowControl(frame, frameWriter);
verify(frameWriter).writeFrame(eq(windowUpdate(CONNECTION_STREAM_ID, windowDelta)));
verify(frameWriter).writeFrame(eq(windowUpdate(STREAM_ID, windowDelta)));
}
@Test
public void initialWindowUpdateShouldAllowMoreFrames() throws Http2Exception {
// Send a frame that takes up the entire window.
int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
Http2DataFrame bigFrame = mockDataFrame(initialWindowSize, false);
controller.applyInboundFlowControl(bigFrame, frameWriter);
// Update the initial window size to allow another frame.
int newInitialWindowSize = 2 * initialWindowSize;
controller.setInitialInboundWindowSize(newInitialWindowSize);
// Clear any previous calls to the writer.
Mockito.reset(frameWriter);
// Send the next frame and verify that the expected window updates were sent.
controller.applyInboundFlowControl(bigFrame, frameWriter);
int delta = newInitialWindowSize - initialWindowSize;
verify(frameWriter).writeFrame(eq(windowUpdate(CONNECTION_STREAM_ID, delta)));
verify(frameWriter).writeFrame(eq(windowUpdate(STREAM_ID, delta)));
}
private int getWindowDelta(int initialSize, int windowSize, int dataSize) {
int newWindowSize = windowSize - dataSize;
return initialSize - newWindowSize;
}
private Http2DataFrame mockDataFrame(int payloadLength, boolean endOfStream) {
Http2DataFrame frame = Mockito.mock(Http2DataFrame.class);
when(frame.getStreamId()).thenReturn(STREAM_ID);
when(frame.isEndOfStream()).thenReturn(endOfStream);
when(frame.content()).thenReturn(buffer);
when(buffer.readableBytes()).thenReturn(payloadLength);
return frame;
}
private void verifyWindowUpdateNotSent() {
verify(frameWriter, never()).writeFrame(any(Http2WindowUpdateFrame.class));
}
private Http2WindowUpdateFrame windowUpdate(int streamId, int delta) {
return new DefaultHttp2WindowUpdateFrame.Builder().setStreamId(streamId)
.setWindowSizeIncrement(delta).build();
}
}

View File

@ -0,0 +1,244 @@
/*
* 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.connection;
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 static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
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 io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.OutboundFlowController.FrameWriter;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.google.common.collect.ImmutableList;
/**
* Tests for {@link DefaultOutboundFlowController}.
*/
public class DefaultOutboundFlowControllerTest {
private static final int STREAM_ID = 1;
private DefaultOutboundFlowController controller;
@Mock
private Http2Connection connection;
@Mock
private ByteBuf buffer;
@Mock
private FrameWriter frameWriter;
@Mock
private Http2Stream stream;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
// Mock the creation of a single stream.
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Listener listener = (Listener) invocation.getArguments()[0];
listener.streamCreated(STREAM_ID);
return null;
}
}).when(connection).addListener(any(Listener.class));
when(connection.getActiveStreams()).thenReturn(ImmutableList.of(stream));
when(stream.getId()).thenReturn(STREAM_ID);
controller = new DefaultOutboundFlowController(connection);
}
@Test
public void frameShouldBeSentImmediately() throws Http2Exception {
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
verify(frameWriter).writeFrame(frame);
assertEquals(1, frame.refCnt());
frame.release();
}
@Test
public void stalledStreamShouldQueueFrame() throws Http2Exception {
controller.setInitialOutboundWindowSize(0);
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
verify(frameWriter, never()).writeFrame(frame);
assertEquals(1, frame.refCnt());
frame.release();
}
@Test
public void nonZeroWindowShouldSendPartialFrame() throws Http2Exception {
controller.setInitialOutboundWindowSize(5);
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
// Verify that a partial frame of 5 was sent.
ArgumentCaptor<Http2DataFrame> argument = ArgumentCaptor.forClass(Http2DataFrame.class);
verify(frameWriter).writeFrame(argument.capture());
Http2DataFrame writtenFrame = argument.getValue();
assertEquals(STREAM_ID, writtenFrame.getStreamId());
assertEquals(5, writtenFrame.content().readableBytes());
assertEquals(2, writtenFrame.refCnt());
assertEquals(2, frame.refCnt());
frame.release(2);
}
@Test
public void initialWindowUpdateShouldSendFrame() throws Http2Exception {
controller.setInitialOutboundWindowSize(0);
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
verify(frameWriter, never()).writeFrame(frame);
// Verify that the entire frame was sent.
controller.setInitialOutboundWindowSize(10);
ArgumentCaptor<Http2DataFrame> argument = ArgumentCaptor.forClass(Http2DataFrame.class);
verify(frameWriter).writeFrame(argument.capture());
Http2DataFrame writtenFrame = argument.getValue();
assertEquals(frame, writtenFrame);
assertEquals(1, frame.refCnt());
frame.release();
}
@Test
public void initialWindowUpdateShouldSendPartialFrame() throws Http2Exception {
controller.setInitialOutboundWindowSize(0);
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
verify(frameWriter, never()).writeFrame(frame);
// Verify that a partial frame of 5 was sent.
controller.setInitialOutboundWindowSize(5);
ArgumentCaptor<Http2DataFrame> argument = ArgumentCaptor.forClass(Http2DataFrame.class);
verify(frameWriter).writeFrame(argument.capture());
Http2DataFrame writtenFrame = argument.getValue();
assertEquals(STREAM_ID, writtenFrame.getStreamId());
assertEquals(5, writtenFrame.content().readableBytes());
assertEquals(2, writtenFrame.refCnt());
assertEquals(2, frame.refCnt());
frame.release(2);
}
@Test
public void connectionWindowUpdateShouldSendFrame() throws Http2Exception {
// Set the connection window size to zero.
controller.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
verify(frameWriter, never()).writeFrame(frame);
// Verify that the entire frame was sent.
controller.updateOutboundWindowSize(CONNECTION_STREAM_ID, 10);
ArgumentCaptor<Http2DataFrame> argument = ArgumentCaptor.forClass(Http2DataFrame.class);
verify(frameWriter).writeFrame(argument.capture());
Http2DataFrame writtenFrame = argument.getValue();
assertEquals(frame, writtenFrame);
assertEquals(1, frame.refCnt());
frame.release();
}
@Test
public void connectionWindowUpdateShouldSendPartialFrame() throws Http2Exception {
// Set the connection window size to zero.
controller.updateOutboundWindowSize(CONNECTION_STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
verify(frameWriter, never()).writeFrame(frame);
// Verify that a partial frame of 5 was sent.
controller.updateOutboundWindowSize(CONNECTION_STREAM_ID, 5);
ArgumentCaptor<Http2DataFrame> argument = ArgumentCaptor.forClass(Http2DataFrame.class);
verify(frameWriter).writeFrame(argument.capture());
Http2DataFrame writtenFrame = argument.getValue();
assertEquals(STREAM_ID, writtenFrame.getStreamId());
assertEquals(5, writtenFrame.content().readableBytes());
assertEquals(2, writtenFrame.refCnt());
assertEquals(2, frame.refCnt());
frame.release(2);
}
@Test
public void streamWindowUpdateShouldSendFrame() throws Http2Exception {
// Set the stream window size to zero.
controller.updateOutboundWindowSize(STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
verify(frameWriter, never()).writeFrame(frame);
// Verify that the entire frame was sent.
controller.updateOutboundWindowSize(STREAM_ID, 10);
ArgumentCaptor<Http2DataFrame> argument = ArgumentCaptor.forClass(Http2DataFrame.class);
verify(frameWriter).writeFrame(argument.capture());
Http2DataFrame writtenFrame = argument.getValue();
assertEquals(frame, writtenFrame);
assertEquals(1, frame.refCnt());
frame.release();
}
@Test
public void streamWindowUpdateShouldSendPartialFrame() throws Http2Exception {
// Set the stream window size to zero.
controller.updateOutboundWindowSize(STREAM_ID, -DEFAULT_FLOW_CONTROL_WINDOW_SIZE);
Http2DataFrame frame = frame(10);
controller.sendFlowControlled(frame, frameWriter);
verify(frameWriter, never()).writeFrame(frame);
// Verify that a partial frame of 5 was sent.
controller.updateOutboundWindowSize(STREAM_ID, 5);
ArgumentCaptor<Http2DataFrame> argument = ArgumentCaptor.forClass(Http2DataFrame.class);
verify(frameWriter).writeFrame(argument.capture());
Http2DataFrame writtenFrame = argument.getValue();
assertEquals(STREAM_ID, writtenFrame.getStreamId());
assertEquals(5, writtenFrame.content().readableBytes());
assertEquals(2, writtenFrame.refCnt());
assertEquals(2, frame.refCnt());
frame.release(2);
}
private Http2DataFrame frame(int payloadLength) {
ByteBuf buffer = Unpooled.buffer(payloadLength);
buffer.writerIndex(payloadLength);
return new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(buffer).build();
}
}

View File

@ -0,0 +1,664 @@
/*
* 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.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 org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
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.handler.codec.http2.draft10.Http2StreamException;
import io.netty.handler.codec.http2.draft10.connection.InboundFlowController.FrameWriter;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2DataFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2GoAwayFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2HeadersFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2PingFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2PriorityFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2PushPromiseFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2RstStreamFrame;
import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2SettingsFrame;
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.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 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}.
*/
public class Http2ConnectionHandlerTest {
private static final int STREAM_ID = 1;
private static final int PUSH_STREAM_ID = 2;
private Http2ConnectionHandler handler;
@Mock
private Http2Connection connection;
@Mock
private Http2Connection.Endpoint remote;
@Mock
private Http2Connection.Endpoint local;
@Mock
private InboundFlowController inboundFlow;
@Mock
private OutboundFlowController outboundFlow;
@Mock
private ChannelHandlerContext ctx;
@Mock
private Channel channel;
@Mock
private ChannelPromise promise;
@Mock
private ChannelFuture future;
@Mock
private Http2Stream stream;
@Mock
private Http2Stream pushStream;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
when(ctx.channel()).thenReturn(channel);
when(ctx.newSucceededFuture()).thenReturn(future);
when(ctx.newPromise()).thenReturn(promise);
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.getStream(STREAM_ID)).thenReturn(stream);
when(connection.getStreamOrFail(STREAM_ID)).thenReturn(stream);
when(connection.local()).thenReturn(local);
when(connection.remote()).thenReturn(remote);
when(local.createStream(eq(STREAM_ID), anyInt(), anyBoolean())).thenReturn(stream);
when(local.reservePushStream(eq(PUSH_STREAM_ID), eq(stream))).thenReturn(pushStream);
when(remote.createStream(eq(STREAM_ID), anyInt(), anyBoolean())).thenReturn(stream);
when(remote.reservePushStream(eq(PUSH_STREAM_ID), eq(stream))).thenReturn(pushStream);
handler = new Http2ConnectionHandler(connection, inboundFlow, outboundFlow);
}
@Test
public void closeShouldSendGoAway() throws Exception {
handler.close(ctx, promise);
verify(connection).sendGoAway(eq(ctx), eq(promise), isNull(Http2Exception.class));
}
@Test
public void channelInactiveShouldCloseStreams() throws Exception {
handler.channelInactive(ctx);
verify(stream).close(eq(ctx), eq(future));
verify(ctx).fireChannelInactive();
}
@Test
public void streamErrorShouldCloseStream() throws Exception {
Http2Exception e = new Http2StreamException(STREAM_ID, PROTOCOL_ERROR);
handler.exceptionCaught(ctx, e);
verify(stream).close(eq(ctx), eq(promise));
verify(ctx).writeAndFlush(eq(createRstStreamFrame(STREAM_ID, PROTOCOL_ERROR)), eq(promise));
verify(ctx).fireExceptionCaught(e);
}
@Test
public void connectionErrorShouldSendGoAway() throws Exception {
Http2Exception e = new Http2Exception(PROTOCOL_ERROR);
handler.exceptionCaught(ctx, e);
verify(connection).sendGoAway(eq(ctx), eq(promise), eq(e));
verify(ctx).fireExceptionCaught(e);
}
@Test
public void inboundDataAfterGoAwayShouldApplyFlowControl() throws Exception {
when(connection.isGoAwaySent()).thenReturn(true);
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(data).build();
handler.channelRead(ctx, frame);
assertEquals(0, data.refCnt());
verify(inboundFlow).applyInboundFlowControl(eq(frame), any(FrameWriter.class));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundDataWithEndOfStreamShouldCloseRemoteSide() throws Exception {
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setEndOfStream(true)
.setContent(data).build();
handler.channelRead(ctx, frame);
assertEquals(1, data.refCnt());
verify(inboundFlow).applyInboundFlowControl(eq(frame), any(FrameWriter.class));
verify(stream).closeRemoteSide(eq(ctx), eq(future));
verify(ctx).fireChannelRead(frame);
data.release();
}
@Test
public void inboundDataShouldSucceed() throws Exception {
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(data).build();
handler.channelRead(ctx, frame);
assertEquals(1, data.refCnt());
verify(inboundFlow).applyInboundFlowControl(eq(frame), any(FrameWriter.class));
verify(stream, never()).closeRemoteSide(eq(ctx), eq(future));
verify(ctx).fireChannelRead(frame);
data.release();
}
@Test
public void inboundHeadersAfterGoAwayShouldBeIgnored() throws Exception {
when(connection.isGoAwaySent()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.channelRead(ctx, frame);
verify(remote, never()).createStream(eq(STREAM_ID), eq(1), eq(false));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundHeadersShouldCreateStream() throws Exception {
int newStreamId = 5;
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(newStreamId).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.channelRead(ctx, frame);
verify(remote).createStream(eq(newStreamId), eq(1), eq(false));
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundHeadersWithEndOfStreamShouldCreateHalfClosedStream() throws Exception {
int newStreamId = 5;
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(newStreamId).setPriority(1)
.setEndOfStream(true).setHeaders(new Http2Headers.Builder().build())
.build();
handler.channelRead(ctx, frame);
verify(remote).createStream(eq(newStreamId), eq(1), eq(true));
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundHeadersWithForPromisedStreamShouldHalfOpenStream() throws Exception {
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.channelRead(ctx, frame);
verify(stream).openForPush();
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundHeadersWithForPromisedStreamShouldCloseStream() throws Exception {
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setEndOfStream(true).setHeaders(new Http2Headers.Builder().build())
.build();
handler.channelRead(ctx, frame);
verify(stream).openForPush();
verify(stream).closeRemoteSide(ctx, future);
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundPushPromiseAfterGoAwayShouldBeIgnored() throws Exception {
when(connection.isGoAwaySent()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2PushPromiseFrame.Builder().setStreamId(STREAM_ID)
.setPromisedStreamId(PUSH_STREAM_ID)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.channelRead(ctx, frame);
verify(remote, never()).reservePushStream(eq(PUSH_STREAM_ID), eq(stream));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundPushPromiseShouldSucceed() throws Exception {
Http2Frame frame =
new DefaultHttp2PushPromiseFrame.Builder().setStreamId(STREAM_ID)
.setPromisedStreamId(PUSH_STREAM_ID)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.channelRead(ctx, frame);
verify(remote).reservePushStream(eq(PUSH_STREAM_ID), eq(stream));
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundPriorityAfterGoAwayShouldBeIgnored() throws Exception {
when(connection.isGoAwaySent()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2PriorityFrame.Builder().setStreamId(STREAM_ID).setPriority(100)
.build();
handler.channelRead(ctx, frame);
verify(stream, never()).setPriority(eq(100));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundPriorityForUnknownStreamShouldBeIgnored() throws Exception {
Http2Frame frame =
new DefaultHttp2PriorityFrame.Builder().setStreamId(5).setPriority(100).build();
handler.channelRead(ctx, frame);
verify(stream, never()).setPriority(eq(100));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundPriorityShouldSucceed() throws Exception {
Http2Frame frame =
new DefaultHttp2PriorityFrame.Builder().setStreamId(STREAM_ID).setPriority(100)
.build();
handler.channelRead(ctx, frame);
verify(stream).setPriority(eq(100));
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundWindowUpdateAfterGoAwayShouldBeIgnored() throws Exception {
when(connection.isGoAwaySent()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2WindowUpdateFrame.Builder().setStreamId(STREAM_ID)
.setWindowSizeIncrement(10).build();
handler.channelRead(ctx, frame);
verify(outboundFlow, never()).updateOutboundWindowSize(eq(STREAM_ID), eq(10));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundWindowUpdateForUnknownStreamShouldBeIgnored() throws Exception {
Http2Frame frame =
new DefaultHttp2WindowUpdateFrame.Builder().setStreamId(5)
.setWindowSizeIncrement(10).build();
handler.channelRead(ctx, frame);
verify(outboundFlow, never()).updateOutboundWindowSize(eq(5), eq(10));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundWindowUpdateShouldSucceed() throws Exception {
Http2Frame frame =
new DefaultHttp2WindowUpdateFrame.Builder().setStreamId(STREAM_ID)
.setWindowSizeIncrement(10).build();
handler.channelRead(ctx, frame);
verify(outboundFlow).updateOutboundWindowSize(eq(STREAM_ID), eq(10));
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundRstStreamAfterGoAwayShouldBeIgnored() throws Exception {
when(connection.isGoAwaySent()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2RstStreamFrame.Builder().setStreamId(STREAM_ID)
.setErrorCode(PROTOCOL_ERROR.getCode()).build();
handler.channelRead(ctx, frame);
verify(stream, never()).close(eq(ctx), eq(future));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundRstStreamForUnknownStreamShouldBeIgnored() throws Exception {
Http2Frame frame =
new DefaultHttp2RstStreamFrame.Builder().setStreamId(5)
.setErrorCode(PROTOCOL_ERROR.getCode()).build();
handler.channelRead(ctx, frame);
verify(stream, never()).close(eq(ctx), eq(future));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundRstStreamShouldCloseStream() throws Exception {
Http2Frame frame =
new DefaultHttp2RstStreamFrame.Builder().setStreamId(STREAM_ID)
.setErrorCode(PROTOCOL_ERROR.getCode()).build();
handler.channelRead(ctx, frame);
verify(stream).close(eq(ctx), eq(future));
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundPingWithAckShouldFireRead() throws Exception {
ByteBuf data = Unpooled.wrappedBuffer(new byte[PING_FRAME_PAYLOAD_LENGTH]);
Http2Frame frame = new DefaultHttp2PingFrame.Builder().setData(data).setAck(true).build();
handler.channelRead(ctx, frame);
verify(ctx, never()).writeAndFlush(any(Http2PingFrame.class));
verify(ctx).fireChannelRead(frame);
}
@Test
public void inboundPingWithoutAckShouldReplyWithAck() throws Exception {
ByteBuf data = Unpooled.wrappedBuffer(new byte[PING_FRAME_PAYLOAD_LENGTH]);
Http2Frame frame = new DefaultHttp2PingFrame.Builder().setData(data).build();
Http2Frame ack = new DefaultHttp2PingFrame.Builder().setData(data).setAck(true).build();
handler.channelRead(ctx, frame);
verify(ctx).writeAndFlush(eq(ack));
verify(ctx, never()).fireChannelRead(frame);
}
@Test
public void inboundSettingsWithAckShouldFireRead() throws Exception {
Http2Frame frame = new DefaultHttp2SettingsFrame.Builder().setAck(true).build();
handler.channelRead(ctx, frame);
verify(remote, never()).setPushToAllowed(anyBoolean());
verify(remote, never()).setMaxStreams(anyInt());
verify(outboundFlow, never()).setInitialOutboundWindowSize(anyInt());
verify(ctx).fireChannelRead(frame);
verify(ctx, never()).writeAndFlush(any(Http2SettingsFrame.class));
}
@Test
public void inboundSettingsShouldSetValues() throws Exception {
Http2Frame frame =
new DefaultHttp2SettingsFrame.Builder().setPushEnabled(true)
.setMaxConcurrentStreams(10).setInitialWindowSize(20).build();
handler.channelRead(ctx, frame);
verify(remote).setPushToAllowed(true);
verify(local).setMaxStreams(10);
verify(outboundFlow).setInitialOutboundWindowSize(20);
verify(ctx, never()).fireChannelRead(frame);
verify(ctx).writeAndFlush(eq(new DefaultHttp2SettingsFrame.Builder().setAck(true).build()));
}
@Test
public void inboundGoAwayShouldUpdateConnectionState() throws Exception {
Http2Frame frame =
new DefaultHttp2GoAwayFrame.Builder().setLastStreamId(1).setErrorCode(2).build();
handler.channelRead(ctx, frame);
verify(connection).goAwayReceived();
verify(ctx).fireChannelRead(frame);
}
@Test
public void outboundDataAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(data).build();
handler.write(ctx, frame, promise);
verify(outboundFlow, never()).sendFlowControlled(eq(frame),
any(OutboundFlowController.FrameWriter.class));
verify(promise).setFailure(any(Http2Exception.class));
assertEquals(0, frame.refCnt());
}
@Test
public void outboundDataShouldApplyFlowControl() throws Exception {
ByteBuf data = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
Http2DataFrame frame =
new DefaultHttp2DataFrame.Builder().setStreamId(STREAM_ID).setContent(data).build();
handler.write(ctx, frame, promise);
verify(outboundFlow).sendFlowControlled(eq(frame),
any(OutboundFlowController.FrameWriter.class));
verify(promise, never()).setFailure(any(Http2Exception.class));
assertEquals(1, frame.refCnt());
frame.release();
}
@Test
public void outboundHeadersAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(ctx, never()).writeAndFlush(frame, promise);
}
@Test
public void outboundHeadersShouldCreateOpenStream() throws Exception {
int newStreamId = 5;
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(newStreamId).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.write(ctx, frame, promise);
verify(local).createStream(eq(newStreamId), eq(1), eq(false));
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(ctx).writeAndFlush(frame, promise);
}
@Test
public void outboundHeadersShouldCreateHalfClosedStream() throws Exception {
int newStreamId = 5;
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(newStreamId).setPriority(1)
.setEndOfStream(true).setHeaders(new Http2Headers.Builder().build())
.build();
handler.write(ctx, frame, promise);
verify(local).createStream(eq(newStreamId), eq(1), eq(true));
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(ctx).writeAndFlush(frame, promise);
}
@Test
public void outboundHeadersShouldOpenStreamForPush() throws Exception {
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.write(ctx, frame, promise);
verify(stream).openForPush();
verify(stream, never()).closeLocalSide(eq(ctx), eq(future));
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(ctx).writeAndFlush(frame, promise);
}
@Test
public void outboundHeadersShouldClosePushStream() throws Exception {
Http2Frame frame =
new DefaultHttp2HeadersFrame.Builder().setStreamId(STREAM_ID).setPriority(1)
.setEndOfStream(true).setHeaders(new Http2Headers.Builder().build())
.build();
handler.write(ctx, frame, promise);
verify(stream).openForPush();
verify(stream).closeLocalSide(eq(ctx), eq(promise));
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(ctx).writeAndFlush(frame, promise);
}
@Test
public void outboundPushPromiseAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2PushPromiseFrame.Builder().setStreamId(STREAM_ID)
.setPromisedStreamId(PUSH_STREAM_ID)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(local, never()).reservePushStream(eq(PUSH_STREAM_ID), eq(stream));
verify(ctx, never()).writeAndFlush(frame, promise);
}
@Test
public void outboundPushPromiseShouldReserveStream() throws Exception {
Http2Frame frame =
new DefaultHttp2PushPromiseFrame.Builder().setStreamId(STREAM_ID)
.setPromisedStreamId(PUSH_STREAM_ID)
.setHeaders(new Http2Headers.Builder().build()).build();
handler.write(ctx, frame, promise);
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(local).reservePushStream(eq(PUSH_STREAM_ID), eq(stream));
verify(ctx).writeAndFlush(frame, promise);
}
@Test
public void outboundPriorityAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2PriorityFrame.Builder().setStreamId(STREAM_ID).setPriority(10)
.build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(stream, never()).setPriority(10);
verify(ctx, never()).writeAndFlush(frame, promise);
}
@Test
public void outboundPriorityShouldSetPriorityOnStream() throws Exception {
Http2Frame frame =
new DefaultHttp2PriorityFrame.Builder().setStreamId(STREAM_ID).setPriority(10)
.build();
handler.write(ctx, frame, promise);
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(stream).setPriority(10);
verify(ctx).writeAndFlush(frame, promise);
}
@Test
public void outboundRstStreamForUnknownStreamShouldIgnore() throws Exception {
Http2Frame frame =
new DefaultHttp2RstStreamFrame.Builder().setStreamId(5).setErrorCode(1).build();
handler.write(ctx, frame, promise);
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(promise).setSuccess();
verify(ctx, never()).writeAndFlush(frame, promise);
}
@Test
public void outboundRstStreamShouldCloseStream() throws Exception {
Http2Frame frame =
new DefaultHttp2RstStreamFrame.Builder().setStreamId(STREAM_ID).setErrorCode(1)
.build();
handler.write(ctx, frame, promise);
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(promise, never()).setSuccess();
verify(stream).close(ctx, promise);
verify(ctx).writeAndFlush(frame, promise);
}
@Test
public void outboundPingAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
ByteBuf data = Unpooled.wrappedBuffer(new byte[PING_FRAME_PAYLOAD_LENGTH]);
Http2Frame frame = new DefaultHttp2PingFrame.Builder().setData(data).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(ctx, never()).writeAndFlush(frame, promise);
}
@Test
public void outboundPingWithAckShouldFail() throws Exception {
ByteBuf data = Unpooled.wrappedBuffer(new byte[PING_FRAME_PAYLOAD_LENGTH]);
Http2Frame frame = new DefaultHttp2PingFrame.Builder().setAck(true).setData(data).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(ctx, never()).writeAndFlush(frame, promise);
}
@Test
public void outboundPingWithAckShouldSend() throws Exception {
ByteBuf data = Unpooled.wrappedBuffer(new byte[PING_FRAME_PAYLOAD_LENGTH]);
Http2Frame frame = new DefaultHttp2PingFrame.Builder().setData(data).build();
handler.write(ctx, frame, promise);
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(ctx).writeAndFlush(frame, promise);
}
@Test
public void outboundGoAwayShouldFail() throws Exception {
Http2Frame frame =
new DefaultHttp2GoAwayFrame.Builder().setLastStreamId(0).setErrorCode(1).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
}
@Test
public void outboundWindowUpdateShouldFail() throws Exception {
Http2Frame frame =
new DefaultHttp2WindowUpdateFrame.Builder().setStreamId(STREAM_ID)
.setWindowSizeIncrement(1).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
}
@Test
public void outboundSettingsAfterGoAwayShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
Http2Frame frame =
new DefaultHttp2SettingsFrame.Builder().setInitialWindowSize(10)
.setMaxConcurrentStreams(20).setPushEnabled(true).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(local, never()).setPushToAllowed(anyBoolean());
verify(remote, never()).setMaxStreams(anyInt());
verify(inboundFlow, never()).setInitialInboundWindowSize(anyInt());
verify(ctx, never()).writeAndFlush(frame, promise);
}
@Test
public void outboundSettingsWithAckShouldFail() throws Exception {
when(connection.isGoAway()).thenReturn(true);
Http2Frame frame = new DefaultHttp2SettingsFrame.Builder().setAck(true).build();
handler.write(ctx, frame, promise);
verify(promise).setFailure(any(Http2Exception.class));
verify(local, never()).setPushToAllowed(anyBoolean());
verify(remote, never()).setMaxStreams(anyInt());
verify(inboundFlow, never()).setInitialInboundWindowSize(anyInt());
verify(ctx, never()).writeAndFlush(frame, promise);
}
@Test
public void outboundSettingsShouldUpdateSettings() throws Exception {
Http2Frame frame =
new DefaultHttp2SettingsFrame.Builder().setInitialWindowSize(10)
.setMaxConcurrentStreams(20).setPushEnabled(true).build();
handler.write(ctx, frame, promise);
verify(promise, never()).setFailure(any(Http2Exception.class));
verify(local).setPushToAllowed(eq(true));
verify(remote).setMaxStreams(eq(20));
verify(inboundFlow).setInitialInboundWindowSize(eq(10));
verify(ctx).writeAndFlush(frame, promise);
}
private DefaultHttp2RstStreamFrame createRstStreamFrame(int streamId, Http2Error error) {
return new DefaultHttp2RstStreamFrame.Builder().setStreamId(streamId)
.setErrorCode(error.getCode()).build();
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.frame;
import static org.junit.Assert.assertEquals;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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;
import io.netty.handler.codec.http2.draft10.frame.encoder.DefaultHttp2HeadersEncoder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Tests for encoding/decoding HTTP2 header blocks.
*/
public class HeaderBlockRoundtripTest {
private DefaultHttp2HeadersDecoder decoder;
private DefaultHttp2HeadersEncoder encoder;
private ByteBuf buffer;
@Before
public void setup() {
encoder = new DefaultHttp2HeadersEncoder();
decoder = new DefaultHttp2HeadersDecoder();
buffer = Unpooled.buffer();
}
@After
public void teardown() {
buffer.release();
}
@Test
public void roundtripShouldBeSuccessful() throws Http2Exception {
Http2Headers in =
new Http2Headers.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();
assertRoundtripSuccessful(in);
}
@Test
public void successiveCallsShouldSucceed() throws Http2Exception {
Http2Headers in =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path")
.addHeader("accept", "*/*").build();
assertRoundtripSuccessful(in);
in =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource1")
.addHeader("accept", "image/jpeg").addHeader("cache-control", "no-cache")
.build();
assertRoundtripSuccessful(in);
in =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2")
.addHeader("accept", "image/png").addHeader("cache-control", "no-cache")
.build();
assertRoundtripSuccessful(in);
}
private void assertRoundtripSuccessful(Http2Headers in) throws Http2Exception {
encoder.encodeHeaders(in, buffer);
Http2Headers out = decoder.decodeHeaders(buffer);
assertEquals(in, out);
}
}

View File

@ -0,0 +1,317 @@
/*
* 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.frame;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.Http2Headers;
import io.netty.util.NetUtil;
import io.netty.util.ReferenceCountUtil;
import java.net.InetSocketAddress;
import org.junit.After;
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 {
private static final EventLoopGroup group = new NioEventLoopGroup();
private CaptureHandler captureHandler;
private ServerBootstrap sb;
private Bootstrap cb;
private Channel serverChannel;
private Channel clientChannel;
@Before
public void setup() throws Exception {
captureHandler = new CaptureHandler();
sb = new ServerBootstrap();
cb = new Bootstrap();
sb.group(new NioEventLoopGroup(), new NioEventLoopGroup());
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast("codec", new Http2FrameCodec());
p.addLast("handler", captureHandler);
}
});
cb.group(new NioEventLoopGroup());
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast("codec", new Http2FrameCodec());
}
});
serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel();
int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port));
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.channel();
}
@After
public void teardown() throws Exception {
serverChannel.close().sync();
sb.group().shutdownGracefully();
cb.group().shutdownGracefully();
}
@AfterClass
public static void destroy() throws Exception {
group.shutdownGracefully().sync();
}
@Test
public void dataFrameShouldMatch() throws Exception {
String text = "hello world";
Http2DataFrame in =
new DefaultHttp2DataFrame.Builder()
.setContent(Unpooled.copiedBuffer(text.getBytes())).setEndOfStream(true)
.setStreamId(0x7FFFFFFF).setPaddingLength(100).build().retain();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertAndReleaseFrames(in, out);
}
@Test
public void headersFrameWithoutPriorityShouldMatch() throws Exception {
Http2Headers headers =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2").build();
Http2HeadersFrame in =
new DefaultHttp2HeadersFrame.Builder().setHeaders(headers).setEndOfStream(true)
.setStreamId(0x7FFFFFFF).build();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertAndReleaseFrames(in, out);
}
@Test
public void headersFrameWithPriorityShouldMatch() throws Exception {
Http2Headers headers =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2").build();
Http2HeadersFrame in =
new DefaultHttp2HeadersFrame.Builder().setHeaders(headers).setEndOfStream(true)
.setStreamId(0x7FFFFFFF).setPriority(0x7FFFFFFF).build();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertAndReleaseFrames(in, out);
}
@Test
public void goAwayFrameShouldMatch() throws Exception {
String text = "test";
Http2GoAwayFrame in =
new DefaultHttp2GoAwayFrame.Builder()
.setDebugData(Unpooled.copiedBuffer(text.getBytes()))
.setLastStreamId(0x7FFFFFFF).setErrorCode(0xFFFFFFFFL).build().retain();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertAndReleaseFrames(in, out);
}
@Test
public void pingFrameShouldMatch() throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("01234567", Charsets.UTF_8);
Http2PingFrame in =
new DefaultHttp2PingFrame.Builder().setAck(true).setData(buf).build().retain();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertAndReleaseFrames(in, out);
}
@Test
public void priorityFrameShouldMatch() throws Exception {
Http2PriorityFrame in =
new DefaultHttp2PriorityFrame.Builder().setStreamId(0x7FFFFFFF)
.setPriority(0x7FFFFFFF).build();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertEquals(in, out);
}
@Test
public void pushPromiseFrameShouldMatch() throws Exception {
Http2Headers headers =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2").build();
Http2PushPromiseFrame in =
new DefaultHttp2PushPromiseFrame.Builder().setHeaders(headers)
.setStreamId(0x7FFFFFFF).setPromisedStreamId(0x7FFFFFFF).build();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertAndReleaseFrames(in, out);
}
@Test
public void rstStreamFrameShouldMatch() throws Exception {
Http2RstStreamFrame in =
new DefaultHttp2RstStreamFrame.Builder().setStreamId(0x7FFFFFFF)
.setErrorCode(0xFFFFFFFFL).build();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertEquals(in, out);
}
@Test
public void settingsFrameShouldMatch() throws Exception {
Http2SettingsFrame in =
new DefaultHttp2SettingsFrame.Builder().setAck(false).setHeaderTableSize(1)
.setInitialWindowSize(Integer.MAX_VALUE).setPushEnabled(true)
.setMaxConcurrentStreams(100L).build();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertEquals(in, out);
}
@Test
public void windowUpdateFrameShouldMatch() throws Exception {
Http2WindowUpdateFrame in =
new DefaultHttp2WindowUpdateFrame.Builder().setStreamId(0x7FFFFFFF)
.setWindowSizeIncrement(0x7FFFFFFF).build();
Http2Frame out = sendAndWaitForFrame(clientChannel, in, captureHandler);
assertEquals(in, out);
}
@Test
public void stressTest() throws Exception {
Http2Headers headers =
new Http2Headers.Builder().setMethod("GET").setScheme("https")
.setAuthority("example.org").setPath("/some/path/resource2").build();
String text = "hello world";
int numStreams = 1000;
for (int i = 1; i < numStreams + 1; ++i) {
Http2HeadersFrame headersFrame =
new DefaultHttp2HeadersFrame.Builder().setHeaders(headers).setStreamId(i)
.build();
Http2DataFrame dataFrame =
new DefaultHttp2DataFrame.Builder()
.setContent(Unpooled.copiedBuffer(text.getBytes()))
.setEndOfStream(true).setStreamId(i).setPaddingLength(100).build()
.retain();
clientChannel.writeAndFlush(headersFrame);
clientChannel.writeAndFlush(dataFrame);
}
// Wait for all frames to be received.
long theFuture = System.currentTimeMillis() + 5000;
int expectedFrames = numStreams * 2;
while (captureHandler.count < expectedFrames && System.currentTimeMillis() < theFuture) {
try {
Thread.sleep(1);
} catch (InterruptedException e) { // Ignore.
}
}
assertEquals(expectedFrames, captureHandler.count);
captureHandler.release();
}
private void assertAndReleaseFrames(Http2Frame in, Http2Frame out) {
assertEquals(in, out);
if (in instanceof ByteBufHolder) {
assertEquals(1, ((ByteBufHolder) in).refCnt());
((ByteBufHolder) in).release();
}
if (out instanceof ByteBufHolder) {
assertEquals(1, ((ByteBufHolder) out).refCnt());
((ByteBufHolder) out).release();
}
}
private static Http2Frame sendAndWaitForFrame(Channel cc, Http2Frame frame,
CaptureHandler captureHandler) {
cc.writeAndFlush(frame);
long theFuture = System.currentTimeMillis() + 3000;
while (captureHandler.frame == null && System.currentTimeMillis() < theFuture) {
try {
Thread.sleep(1);
} catch (InterruptedException e) { // Ignore.
}
}
assertNotNull("not null frame", captureHandler.frame);
return captureHandler.frame;
}
private static class CaptureHandler extends ChannelHandlerAdapter {
public volatile Http2Frame frame;
public volatile int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Release any allocated data for the previous frame if there was one.
release();
// Copy the frame if it contains allocated data.
if (msg instanceof ByteBufHolder) {
ByteBufHolder holder = (ByteBufHolder) msg;
msg = holder.copy();
holder.release();
}
this.frame = (Http2Frame) msg;
count++;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
}
public void release() {
if (frame != null) {
ReferenceCountUtil.release(frame);
frame = null;
}
}
}
}

View File

@ -157,6 +157,7 @@
<module>buffer</module>
<module>codec</module>
<module>codec-http</module>
<module>codec-http2</module>
<module>codec-memcache</module>
<module>codec-socks</module>
<module>transport</module>