Showing preview only (323K chars total). Download the full file or copy to clipboard to get everything.
Repository: twitter/netty-http2
Branch: master
Commit: e8df896db610
Files: 54
Total size: 303.3 KB
Directory structure:
gitextract_lusmlr8n/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
└── src/
├── main/
│ └── java/
│ └── com/
│ └── twitter/
│ └── http2/
│ ├── DefaultHttp2Headers.java
│ ├── DefaultHttpDataFrame.java
│ ├── DefaultHttpGoAwayFrame.java
│ ├── DefaultHttpHeaderBlockFrame.java
│ ├── DefaultHttpHeadersFrame.java
│ ├── DefaultHttpPingFrame.java
│ ├── DefaultHttpPriorityFrame.java
│ ├── DefaultHttpPushPromiseFrame.java
│ ├── DefaultHttpRstStreamFrame.java
│ ├── DefaultHttpSettingsFrame.java
│ ├── DefaultHttpStreamFrame.java
│ ├── DefaultHttpWindowUpdateFrame.java
│ ├── HttpCodecUtil.java
│ ├── HttpConnection.java
│ ├── HttpConnectionHandler.java
│ ├── HttpDataFrame.java
│ ├── HttpErrorCode.java
│ ├── HttpFrame.java
│ ├── HttpFrameDecoder.java
│ ├── HttpFrameDecoderDelegate.java
│ ├── HttpFrameEncoder.java
│ ├── HttpGoAwayFrame.java
│ ├── HttpHeaderBlockDecoder.java
│ ├── HttpHeaderBlockEncoder.java
│ ├── HttpHeaderBlockFrame.java
│ ├── HttpHeadersFrame.java
│ ├── HttpMessageProxy.java
│ ├── HttpPingFrame.java
│ ├── HttpPriorityFrame.java
│ ├── HttpProtocolException.java
│ ├── HttpPushPromiseFrame.java
│ ├── HttpRequestProxy.java
│ ├── HttpResponseProxy.java
│ ├── HttpRstStreamFrame.java
│ ├── HttpSettingsFrame.java
│ ├── HttpStreamDecoder.java
│ ├── HttpStreamEncoder.java
│ ├── HttpStreamFrame.java
│ ├── HttpWindowUpdateFrame.java
│ ├── Pipe.java
│ ├── StreamedHttpMessage.java
│ ├── StreamedHttpRequest.java
│ └── StreamedHttpResponse.java
└── test/
└── java/
└── com/
└── twitter/
└── http2/
├── HttpFrameDecoderTest.java
├── HttpFrameEncoderTest.java
├── HttpHeaderCompressionTest.java
├── HttpRequestProxyTest.java
├── HttpResponseProxyTest.java
└── PipeTest.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
target
.idea
*.iml
================================================
FILE: .travis.yml
================================================
language: java
sudo: false
jdk:
- openjdk7
after_success:
- mvn clean cobertura:cobertura coveralls:cobertura
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed 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.
================================================
FILE: README.md
================================================
# netty-http2 [](https://travis-ci.org/twitter/netty-http2)
Legacy HTTP/2 codec for Netty. Use the codec shipped with Netty 4.1 instead https://github.com/netty/netty/tree/4.1/codec-http2
================================================
FILE: pom.xml
================================================
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.twitter</groupId>
<artifactId>netty-http2</artifactId>
<version>0.17.12-SNAPSHOT</version>
<name>HTTP/2</name>
<url>http://github.com/twitter/netty-http2</url>
<description>HTTP/2 for Netty</description>
<scm>
<connection>scm:git:git@github.com:twitter/netty-http2.git</connection>
<url>scm:git:git@github.com:twitter/netty-http2.git</url>
<developerConnection>scm:git:git@github.com:twitter/netty-http2.git</developerConnection>
</scm>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<name>Jeff Pinner</name>
<email>jpinner@twitter.com</email>
</developer>
</developers>
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<encoding>UTF-8</encoding>
</properties>
<distributionManagement>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype OSS</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>sonatype-nexus-staging</id>
<name>Nexus Release Repository</name>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>com.twitter</groupId>
<artifactId>hpack</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>4.1.0.CR4</version>
</dependency>
<!-- Test Deps -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.1</version>
<configuration>
<mavenExecutorId>forked-path</mavenExecutorId>
<useReleaseProfile>false</useReleaseProfile>
<arguments>-Psonatype-oss-release</arguments>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<argLine>-Xmx1024m</argLine>
<redirectTestOutputToFile>false</redirectTestOutputToFile>
<includes>
<include>**/Test*.java</include>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<format>xml</format>
<maxmem>256m</maxmem>
<aggregate>true</aggregate>
</configuration>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>2.2.0</version>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>sonatype-oss-release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttp2Headers.java
================================================
package com.twitter.http2;
import io.netty.handler.codec.DefaultHeaders.NameValidator;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.util.AsciiString;
import io.netty.util.ByteProcessor;
import io.netty.util.internal.PlatformDependent;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
public class DefaultHttp2Headers extends DefaultHttpHeaders {
private static final ByteProcessor HEADER_NAME_VALIDATOR = new ByteProcessor() {
@Override
public boolean process(byte value) throws Exception {
validateChar((char) (value & 0xFF));
return true;
}
};
private static void validateChar(char character) {
switch (character) {
case '\t':
case '\n':
case 0x0b:
case '\f':
case '\r':
case ' ':
case ',':
case ';':
case '=':
throw new IllegalArgumentException(
"a header name cannot contain the following prohibited characters: =,; \\t\\r\\n\\v\\f: " +
character);
default:
// Check to see if the character is not an ASCII character, or invalid
if (character > 127) {
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
character);
}
}
}
static final NameValidator<CharSequence> Http2NameValidator = new NameValidator<CharSequence>() {
@Override
public void validateName(CharSequence name) {
if (name instanceof AsciiString) {
try {
((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR);
} catch (Exception e) {
PlatformDependent.throwException(e);
}
} else {
checkNotNull(name, "name");
// Go through each character in the name
for (int index = 0; index < name.length(); ++index) {
validateChar(name.charAt(index));
}
}
}
};
public DefaultHttp2Headers() {
super(true, Http2NameValidator);
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpDataFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.buffer.Unpooled;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpDataFrame} implementation.
*/
public class DefaultHttpDataFrame extends DefaultHttpStreamFrame implements HttpDataFrame {
private final ByteBuf data;
private boolean last;
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
*/
public DefaultHttpDataFrame(int streamId) {
this(streamId, Unpooled.buffer(0));
}
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
* @param data the payload of the frame. Can not exceed {@link HttpCodecUtil#HTTP_MAX_LENGTH}
*/
public DefaultHttpDataFrame(int streamId, ByteBuf data) {
super(streamId);
if (data == null) {
throw new NullPointerException("data");
}
this.data = validate(data);
}
private static ByteBuf validate(ByteBuf data) {
if (data.readableBytes() > HttpCodecUtil.HTTP_MAX_LENGTH) {
throw new IllegalArgumentException("data payload cannot exceed "
+ HttpCodecUtil.HTTP_MAX_LENGTH + " bytes");
}
return data;
}
@Override
public boolean isLast() {
return last;
}
@Override
public HttpDataFrame setLast(boolean last) {
this.last = last;
return this;
}
@Override
public HttpDataFrame setStreamId(int streamId) {
super.setStreamId(streamId);
return this;
}
@Override
public ByteBuf content() {
if (data.refCnt() <= 0) {
throw new IllegalReferenceCountException(data.refCnt());
}
return data;
}
@Override
public HttpDataFrame copy() {
HttpDataFrame frame = new DefaultHttpDataFrame(getStreamId(), content().copy());
frame.setLast(isLast());
return frame;
}
@Override
public HttpDataFrame duplicate() {
HttpDataFrame frame = new DefaultHttpDataFrame(getStreamId(), content().duplicate());
frame.setLast(isLast());
return frame;
}
@Override
public int refCnt() {
return data.refCnt();
}
@Override
public HttpDataFrame retain() {
data.retain();
return this;
}
@Override
public HttpDataFrame retain(int increment) {
data.retain(increment);
return this;
}
@Override
public boolean release() {
return data.release();
}
@Override
public boolean release(int decrement) {
return data.release(decrement);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append("(last: ");
buf.append(isLast());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append("--> Stream-ID = ");
buf.append(getStreamId());
buf.append(StringUtil.NEWLINE);
buf.append("--> Size = ");
if (refCnt() == 0) {
buf.append("(freed)");
} else {
buf.append(content().readableBytes());
}
return buf.toString();
}
@Override
public ByteBufHolder touch() {
data.touch();
return this;
}
@Override
public ByteBufHolder touch(Object o) {
data.touch(o);
return this;
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpGoAwayFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpGoAwayFrame} implementation.
*/
public class DefaultHttpGoAwayFrame implements HttpGoAwayFrame {
private int lastStreamId;
private HttpErrorCode errorCode;
/**
* Creates a new instance.
*
* @param lastStreamId the Last-Stream-ID of this frame
* @param code the error code of this frame
*/
public DefaultHttpGoAwayFrame(int lastStreamId, int code) {
this(lastStreamId, HttpErrorCode.valueOf(code));
}
/**
* Creates a new instance.
*
* @param lastStreamId the Last-Stream-ID of this frame
* @param errorCode the error code of this frame
*/
public DefaultHttpGoAwayFrame(int lastStreamId, HttpErrorCode errorCode) {
setLastStreamId(lastStreamId);
setErrorCode(errorCode);
}
@Override
public int getLastStreamId() {
return lastStreamId;
}
@Override
public HttpGoAwayFrame setLastStreamId(int lastStreamId) {
if (lastStreamId < 0) {
throw new IllegalArgumentException(
"Last-Stream-ID cannot be negative: " + lastStreamId);
}
this.lastStreamId = lastStreamId;
return this;
}
@Override
public HttpErrorCode getErrorCode() {
return errorCode;
}
@Override
public HttpGoAwayFrame setErrorCode(HttpErrorCode errorCode) {
this.errorCode = errorCode;
return this;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append(StringUtil.NEWLINE);
buf.append("--> Last-Stream-ID = ");
buf.append(getLastStreamId());
buf.append(StringUtil.NEWLINE);
buf.append("--> Error Code: ");
buf.append(getErrorCode().toString());
return buf.toString();
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpHeaderBlockFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.util.internal.StringUtil;
import java.util.Map;
/**
* The default {@link HttpHeaderBlockFrame} implementation.
*/
public abstract class DefaultHttpHeaderBlockFrame extends DefaultHttpStreamFrame
implements HttpHeaderBlockFrame {
private boolean invalid;
private boolean truncated;
private final HttpHeaders headers = new DefaultHttp2Headers();
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
*/
protected DefaultHttpHeaderBlockFrame(int streamId) {
super(streamId);
}
@Override
public boolean isInvalid() {
return invalid;
}
@Override
public HttpHeaderBlockFrame setInvalid() {
invalid = true;
return this;
}
@Override
public boolean isTruncated() {
return truncated;
}
@Override
public HttpHeaderBlockFrame setTruncated() {
truncated = true;
return this;
}
@Override
public HttpHeaders headers() {
return headers;
}
protected void appendHeaders(StringBuilder buf) {
for (Map.Entry<String, String> e : headers()) {
buf.append(" ");
buf.append(e.getKey());
buf.append(": ");
buf.append(e.getValue());
buf.append(StringUtil.NEWLINE);
}
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpHeadersFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.util.internal.StringUtil;
import static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_DEPENDENCY;
import static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;
/**
* The default {@link HttpHeadersFrame} implementation.
*/
public class DefaultHttpHeadersFrame extends DefaultHttpHeaderBlockFrame
implements HttpHeadersFrame {
private boolean last;
private boolean exclusive = false;
private int dependency = HTTP_DEFAULT_DEPENDENCY;
private int weight = HTTP_DEFAULT_WEIGHT;
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
*/
public DefaultHttpHeadersFrame(int streamId) {
super(streamId);
}
@Override
public boolean isLast() {
return last;
}
@Override
public HttpHeadersFrame setLast(boolean last) {
this.last = last;
return this;
}
@Override
public boolean isExclusive() {
return exclusive;
}
@Override
public HttpHeadersFrame setExclusive(boolean exclusive) {
this.exclusive = exclusive;
return this;
}
@Override
public int getDependency() {
return dependency;
}
@Override
public HttpHeadersFrame setDependency(int dependency) {
if (dependency < 0) {
throw new IllegalArgumentException(
"Dependency cannot be negative: " + dependency);
}
this.dependency = dependency;
return this;
}
@Override
public int getWeight() {
return weight;
}
@Override
public HttpHeadersFrame setWeight(int weight) {
if (weight <= 0 || weight > 256) {
throw new IllegalArgumentException(
"Illegal weight: " + weight);
}
this.weight = weight;
return this;
}
@Override
public HttpHeadersFrame setStreamId(int streamId) {
super.setStreamId(streamId);
return this;
}
@Override
public HttpHeadersFrame setInvalid() {
super.setInvalid();
return this;
}
@Override
public HttpHeadersFrame setTruncated() {
super.setTruncated();
return this;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append("(last: ");
buf.append(isLast());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append("--> Stream-ID = ");
buf.append(getStreamId());
buf.append(StringUtil.NEWLINE);
buf.append("--> Dependency = ");
buf.append(getDependency());
buf.append(" (exclusive: ");
buf.append(isExclusive());
buf.append(", weight: ");
buf.append(getWeight());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append("--> Headers:");
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
// Remove the last newline.
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpPingFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpPingFrame} implementation.
*/
public class DefaultHttpPingFrame implements HttpPingFrame {
private long data;
private boolean pong;
/**
* Creates a new instance.
*
* @param data the data payload of this frame
*/
public DefaultHttpPingFrame(long data) {
setData(data);
}
@Override
public long getData() {
return data;
}
@Override
public HttpPingFrame setData(long data) {
this.data = data;
return this;
}
@Override
public boolean isPong() {
return pong;
}
@Override
public HttpPingFrame setPong(boolean pong) {
this.pong = pong;
return this;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append("(pong: ");
buf.append(isPong());
buf.append(')');
buf.append(StringUtil.NEWLINE);
buf.append("--> Data = ");
buf.append(getData());
return buf.toString();
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpPriorityFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpPriorityFrame} implementation.
*/
public class DefaultHttpPriorityFrame implements HttpPriorityFrame {
private int streamId;
private boolean exclusive;
private int dependency;
private int weight;
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
* @param exclusive if the dependency of the stream is exclusive
* @param dependency the dependency of the stream
* @param weight the weight of the dependency of the stream
*/
public DefaultHttpPriorityFrame(int streamId, boolean exclusive, int dependency, int weight) {
setStreamId(streamId);
setExclusive(exclusive);
setDependency(dependency);
setWeight(weight);
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public HttpPriorityFrame setStreamId(int streamId) {
if (streamId <= 0) {
throw new IllegalArgumentException(
"Stream identifier must be positive: " + streamId);
}
this.streamId = streamId;
return this;
}
@Override
public boolean isExclusive() {
return exclusive;
}
@Override
public HttpPriorityFrame setExclusive(boolean exclusive) {
this.exclusive = exclusive;
return this;
}
@Override
public int getDependency() {
return dependency;
}
@Override
public HttpPriorityFrame setDependency(int dependency) {
if (dependency < 0) {
throw new IllegalArgumentException(
"Dependency cannot be negative: " + dependency);
}
this.dependency = dependency;
return this;
}
@Override
public int getWeight() {
return weight;
}
@Override
public HttpPriorityFrame setWeight(int weight) {
if (weight <= 0 || weight > 256) {
throw new IllegalArgumentException(
"Illegal weight: " + weight);
}
this.weight = weight;
return this;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append(StringUtil.NEWLINE);
buf.append("--> Stream-ID = ");
buf.append(getStreamId());
buf.append(StringUtil.NEWLINE);
buf.append("--> Dependency = ");
buf.append(getDependency());
buf.append(" (exclusive: ");
buf.append(isExclusive());
buf.append(", weight: ");
buf.append(getWeight());
buf.append(')');
return buf.toString();
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpPushPromiseFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpPushPromiseFrame} implementation.
*/
public class DefaultHttpPushPromiseFrame extends DefaultHttpHeaderBlockFrame
implements HttpPushPromiseFrame {
private int promisedStreamId;
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
*/
public DefaultHttpPushPromiseFrame(int streamId, int promisedStreamId) {
super(streamId);
setPromisedStreamId(promisedStreamId);
}
@Override
public int getPromisedStreamId() {
return promisedStreamId;
}
@Override
public HttpPushPromiseFrame setPromisedStreamId(int promisedStreamId) {
if (promisedStreamId <= 0) {
throw new IllegalArgumentException(
"Promised-Stream-ID must be positive: " + promisedStreamId);
}
this.promisedStreamId = promisedStreamId;
return this;
}
@Override
public HttpPushPromiseFrame setStreamId(int streamId) {
super.setStreamId(streamId);
return this;
}
@Override
public HttpPushPromiseFrame setInvalid() {
super.setInvalid();
return this;
}
@Override
public HttpPushPromiseFrame setTruncated() {
super.setTruncated();
return this;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append(StringUtil.NEWLINE);
buf.append("--> Stream-ID = ");
buf.append(getStreamId());
buf.append(StringUtil.NEWLINE);
buf.append("--> Promised-Stream-ID = ");
buf.append(getPromisedStreamId());
buf.append(StringUtil.NEWLINE);
buf.append("--> Headers:");
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);
// Remove the last newline.
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpRstStreamFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpRstStreamFrame} implementation.
*/
public class DefaultHttpRstStreamFrame implements HttpRstStreamFrame {
private int streamId;
private HttpErrorCode errorCode;
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
* @param code the error code of this frame
*/
public DefaultHttpRstStreamFrame(int streamId, int code) {
this(streamId, HttpErrorCode.valueOf(code));
}
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
* @param errorCode the error code of this frame
*/
public DefaultHttpRstStreamFrame(int streamId, HttpErrorCode errorCode) {
setStreamId(streamId);
setErrorCode(errorCode);
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public HttpRstStreamFrame setStreamId(int streamId) {
if (streamId <= 0) {
throw new IllegalArgumentException(
"Stream identifier must be positive: " + streamId);
}
this.streamId = streamId;
return this;
}
@Override
public HttpErrorCode getErrorCode() {
return errorCode;
}
@Override
public HttpRstStreamFrame setErrorCode(HttpErrorCode errorCode) {
this.errorCode = errorCode;
return this;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append(StringUtil.NEWLINE);
buf.append("--> Stream-ID = ");
buf.append(getStreamId());
buf.append(StringUtil.NEWLINE);
buf.append("--> Error Code: ");
buf.append(getErrorCode().toString());
return buf.toString();
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpSettingsFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpSettingsFrame} implementation.
*/
public class DefaultHttpSettingsFrame implements HttpSettingsFrame {
private final Map<Integer, Integer> settingsMap = new TreeMap<Integer, Integer>();
private boolean ack;
@Override
public Set<Integer> getIds() {
return settingsMap.keySet();
}
@Override
public boolean isSet(int id) {
return settingsMap.containsKey(id);
}
@Override
public int getValue(int id) {
if (settingsMap.containsKey(id)) {
return settingsMap.get(id);
} else {
return -1;
}
}
@Override
public HttpSettingsFrame setValue(int id, int value) {
if (id < 0 || id > HttpCodecUtil.HTTP_SETTINGS_MAX_ID) {
throw new IllegalArgumentException("Setting ID is not valid: " + id);
}
settingsMap.put(id, value);
return this;
}
@Override
public HttpSettingsFrame removeValue(int id) {
settingsMap.remove(id);
return this;
}
@Override
public boolean isAck() {
return ack;
}
@Override
public HttpSettingsFrame setAck(boolean ack) {
this.ack = ack;
return this;
}
private Set<Map.Entry<Integer, Integer>> getSettings() {
return settingsMap.entrySet();
}
private void appendSettings(StringBuilder buf) {
for (Map.Entry<Integer, Integer> e : getSettings()) {
buf.append("--> ");
buf.append(e.getKey().toString());
buf.append(':');
buf.append(e.getValue().toString());
buf.append(StringUtil.NEWLINE);
}
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append("(ack: ");
buf.append(isAck());
buf.append(')');
buf.append(StringUtil.NEWLINE);
appendSettings(buf);
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
return buf.toString();
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpStreamFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
/**
* The default {@link HttpStreamFrame} implementation.
*/
public abstract class DefaultHttpStreamFrame implements HttpStreamFrame {
private int streamId;
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
*/
protected DefaultHttpStreamFrame(int streamId) {
setStreamId(streamId);
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public HttpStreamFrame setStreamId(int streamId) {
if (streamId <= 0) {
throw new IllegalArgumentException(
"Stream identifier must be positive: " + streamId);
}
this.streamId = streamId;
return this;
}
}
================================================
FILE: src/main/java/com/twitter/http2/DefaultHttpWindowUpdateFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.util.internal.StringUtil;
/**
* The default {@link HttpWindowUpdateFrame} implementation.
*/
public class DefaultHttpWindowUpdateFrame implements HttpWindowUpdateFrame {
private int streamId;
private int windowSizeIncrement;
/**
* Creates a new instance.
*
* @param streamId the stream identifier of this frame
* @param windowSizeIncrement the Window-Size-Increment of this frame
*/
public DefaultHttpWindowUpdateFrame(int streamId, int windowSizeIncrement) {
setStreamId(streamId);
setWindowSizeIncrement(windowSizeIncrement);
}
@Override
public int getStreamId() {
return streamId;
}
@Override
public DefaultHttpWindowUpdateFrame setStreamId(int streamId) {
if (streamId < 0) {
throw new IllegalArgumentException(
"Stream identifier cannot be negative: " + streamId);
}
this.streamId = streamId;
return this;
}
@Override
public int getWindowSizeIncrement() {
return windowSizeIncrement;
}
@Override
public DefaultHttpWindowUpdateFrame setWindowSizeIncrement(int windowSizeIncrement) {
if (windowSizeIncrement <= 0) {
throw new IllegalArgumentException(
"Window-Size-Increment must be positive: " + windowSizeIncrement);
}
this.windowSizeIncrement = windowSizeIncrement;
return this;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.simpleClassName(this));
buf.append(StringUtil.NEWLINE);
buf.append("--> Stream-ID = ");
buf.append(getStreamId());
buf.append(StringUtil.NEWLINE);
buf.append("--> Window-Size-Increment = ");
buf.append(getWindowSizeIncrement());
return buf.toString();
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpCodecUtil.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.buffer.ByteBuf;
final class HttpCodecUtil {
static final int HTTP_FRAME_HEADER_SIZE = 9;
static final int HTTP_MAX_LENGTH = 0x4000; // Initial MAX_FRAME_SIZE value is 2^14
static final int HTTP_DATA_FRAME = 0x00;
static final int HTTP_HEADERS_FRAME = 0x01;
static final int HTTP_PRIORITY_FRAME = 0x02;
static final int HTTP_RST_STREAM_FRAME = 0x03;
static final int HTTP_SETTINGS_FRAME = 0x04;
static final int HTTP_PUSH_PROMISE_FRAME = 0x05;
static final int HTTP_PING_FRAME = 0x06;
static final int HTTP_GOAWAY_FRAME = 0x07;
static final int HTTP_WINDOW_UPDATE_FRAME = 0x08;
static final int HTTP_CONTINUATION_FRAME = 0x09;
static final byte HTTP_FLAG_ACK = 0x01;
static final byte HTTP_FLAG_END_STREAM = 0x01;
static final byte HTTP_FLAG_END_SEGMENT = 0x02;
static final byte HTTP_FLAG_END_HEADERS = 0x04;
static final byte HTTP_FLAG_PADDED = 0x08;
static final byte HTTP_FLAG_PRIORITY = 0x20;
static final int HTTP_DEFAULT_WEIGHT = 16;
static final int HTTP_DEFAULT_DEPENDENCY = 0;
static final int HTTP_SETTINGS_MAX_ID = 0xFFFF; // Identifier is a 16-bit field
static final int HTTP_CONNECTION_STREAM_ID = 0;
/**
* Reads a big-endian unsigned short integer from the buffer.
*/
static int getUnsignedShort(ByteBuf buf, int offset) {
return (buf.getByte(offset) & 0xFF) << 8
| buf.getByte(offset + 1) & 0xFF;
}
/**
* Reads a big-endian unsigned medium integer from the buffer.
*/
static int getUnsignedMedium(ByteBuf buf, int offset) {
return (buf.getByte(offset) & 0xFF) << 16
| (buf.getByte(offset + 1) & 0xFF) << 8
| buf.getByte(offset + 2) & 0xFF;
}
/**
* Reads a big-endian (31-bit) integer from the buffer.
*/
static int getUnsignedInt(ByteBuf buf, int offset) {
return (buf.getByte(offset) & 0x7F) << 24
| (buf.getByte(offset + 1) & 0xFF) << 16
| (buf.getByte(offset + 2) & 0xFF) << 8
| buf.getByte(offset + 3) & 0xFF;
}
/**
* Reads a big-endian signed integer from the buffer.
*/
static int getSignedInt(ByteBuf buf, int offset) {
return (buf.getByte(offset) & 0xFF) << 24
| (buf.getByte(offset + 1) & 0xFF) << 16
| (buf.getByte(offset + 2) & 0xFF) << 8
| buf.getByte(offset + 3) & 0xFF;
}
/**
* Reads a big-endian signed long from the buffer.
*/
static long getSignedLong(ByteBuf buf, int offset) {
return ((long) buf.getByte(offset) & 0xFF) << 56
| ((long) buf.getByte(offset + 1) & 0xFF) << 48
| ((long) buf.getByte(offset + 2) & 0xFF) << 40
| ((long) buf.getByte(offset + 3) & 0xFF) << 32
| ((long) buf.getByte(offset + 4) & 0xFF) << 24
| ((long) buf.getByte(offset + 5) & 0xFF) << 16
| ((long) buf.getByte(offset + 6) & 0xFF) << 8
| (long) buf.getByte(offset + 7) & 0xFF;
}
/**
* Returns {@code true} if the stream identifier is for a server initiated stream.
*/
static boolean isServerId(int streamId) {
// Server initiated streams have even stream identifiers
return streamId % 2 == 0;
}
private HttpCodecUtil() {
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpConnection.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.channel.ChannelPromise;
import io.netty.util.internal.EmptyArrays;
import static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;
import static com.twitter.http2.HttpCodecUtil.HTTP_CONNECTION_STREAM_ID;
final class HttpConnection {
private static final HttpProtocolException STREAM_CLOSED =
new HttpProtocolException("Stream closed");
static {
STREAM_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
}
private final AtomicInteger activeLocalStreams = new AtomicInteger();
private final AtomicInteger activeRemoteStreams = new AtomicInteger();
private final Map<Integer, Node> streams = new ConcurrentHashMap<Integer, Node>();
private final AtomicInteger sendWindowSize;
private final AtomicInteger receiveWindowSize;
public HttpConnection(int sendWindowSize, int receiveWindowSize) {
streams.put(HTTP_CONNECTION_STREAM_ID, new Node(null));
this.sendWindowSize = new AtomicInteger(sendWindowSize);
this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
}
int numActiveStreams(boolean remote) {
if (remote) {
return activeRemoteStreams.get();
} else {
return activeLocalStreams.get();
}
}
boolean noActiveStreams() {
return activeRemoteStreams.get() + activeLocalStreams.get() == 0;
}
void acceptStream(
int streamId, boolean remoteSideClosed, boolean localSideClosed,
int streamSendWindowSize, int streamReceiveWindowSize, boolean remote) {
StreamState state = null;
if (!remoteSideClosed || !localSideClosed) {
state = new StreamState(
remoteSideClosed, localSideClosed, streamSendWindowSize, streamReceiveWindowSize);
}
Node node = new Node(state);
node.parent = streams.get(HTTP_CONNECTION_STREAM_ID);
streams.put(streamId, node);
if (state != null) {
if (remote) {
activeRemoteStreams.incrementAndGet();
} else {
activeLocalStreams.incrementAndGet();
}
}
}
private StreamState removeActiveStream(int streamId, boolean remote) {
Node stream = streams.remove(streamId);
if (stream != null && stream.state != null) {
StreamState state = stream.state;
stream.close();
if (remote) {
activeRemoteStreams.decrementAndGet();
} else {
activeLocalStreams.decrementAndGet();
}
return state;
}
return null;
}
void removeStream(int streamId, boolean remote) {
StreamState state = removeActiveStream(streamId, remote);
if (state != null) {
state.clearPendingWrites(STREAM_CLOSED);
}
}
boolean isRemoteSideClosed(int streamId) {
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
return state == null || state.isRemoteSideClosed();
}
void closeRemoteSide(int streamId, boolean remote) {
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
if (state != null) {
state.closeRemoteSide();
if (state.isLocalSideClosed()) {
removeActiveStream(streamId, remote);
}
}
}
boolean isLocalSideClosed(int streamId) {
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
return state == null || state.isLocalSideClosed();
}
void closeLocalSide(int streamId, boolean remote) {
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
if (state != null) {
state.closeLocalSide();
if (state.isRemoteSideClosed()) {
removeActiveStream(streamId, remote);
}
}
}
int getSendWindowSize(int streamId) {
if (streamId == HTTP_CONNECTION_STREAM_ID) {
return sendWindowSize.get();
}
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
return state != null ? state.getSendWindowSize() : -1;
}
int updateSendWindowSize(int streamId, int deltaWindowSize) {
if (streamId == HTTP_CONNECTION_STREAM_ID) {
return sendWindowSize.addAndGet(deltaWindowSize);
}
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
return state != null ? state.updateSendWindowSize(deltaWindowSize) : -1;
}
int updateReceiveWindowSize(int streamId, int deltaWindowSize) {
if (streamId == HTTP_CONNECTION_STREAM_ID) {
return receiveWindowSize.addAndGet(deltaWindowSize);
}
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
if (state == null) {
return -1;
}
if (deltaWindowSize > 0) {
state.setReceiveWindowSizeLowerBound(0);
}
return state.updateReceiveWindowSize(deltaWindowSize);
}
int getReceiveWindowSizeLowerBound(int streamId) {
if (streamId == HTTP_CONNECTION_STREAM_ID) {
return 0;
}
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
return state != null ? state.getReceiveWindowSizeLowerBound() : 0;
}
void updateAllSendWindowSizes(int deltaWindowSize) {
for (Node stream : streams.values()) {
StreamState state = stream.state;
if (state != null) {
state.updateSendWindowSize(deltaWindowSize);
}
}
}
void updateAllReceiveWindowSizes(int deltaWindowSize) {
for (Node stream : streams.values()) {
StreamState state = stream.state;
if (state != null) {
state.updateReceiveWindowSize(deltaWindowSize);
if (deltaWindowSize < 0) {
state.setReceiveWindowSizeLowerBound(deltaWindowSize);
}
}
}
}
boolean putPendingWrite(int streamId, PendingWrite evt) {
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
return state != null && state.putPendingWrite(evt);
}
PendingWrite getPendingWrite(int streamId) {
if (streamId == HTTP_CONNECTION_STREAM_ID) {
Node connection = streams.get(HTTP_CONNECTION_STREAM_ID);
return getPendingWrite(connection);
}
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
return state != null ? state.getPendingWrite() : null;
}
PendingWrite getPendingWrite(Node node) {
PendingWrite e = null;
if (node.state != null && node.state.getSendWindowSize() > 0) {
e = node.state.getPendingWrite();
}
if (e == null) {
for (Node child : node.children) {
e = getPendingWrite(child);
if (e != null) {
break;
}
}
}
return e;
}
PendingWrite removePendingWrite(int streamId) {
Node stream = streams.get(streamId);
StreamState state = stream == null ? null : stream.state;
return state != null ? state.removePendingWrite() : null;
}
/**
* Set the priority of the stream.
*/
boolean setPriority(int streamId, boolean exclusive, int dependency, int weight) {
Node stream = streams.get(streamId);
if (stream == null) {
// stream closed?
return false;
}
Node parent = streams.get(dependency);
if (parent == null) {
// garbage collected?
stream.parent.removeDependent(stream);
// set to default priority
Node root = streams.get(HTTP_CONNECTION_STREAM_ID);
root.addDependent(false, stream);
stream.setWeight(HTTP_DEFAULT_WEIGHT);
return false;
}
// check if we need to restructure the tree
if (parent == stream.parent) {
if (exclusive) {
// move dependents to stream
parent.addDependent(true, stream);
}
} else {
stream.parent.removeDependent(stream);
parent.addDependent(exclusive, stream);
}
stream.setWeight(weight);
return true;
}
private static final class StreamState {
private boolean remoteSideClosed;
private boolean localSideClosed;
private final AtomicInteger sendWindowSize;
private final AtomicInteger receiveWindowSize;
private int receiveWindowSizeLowerBound;
private final ConcurrentLinkedQueue<PendingWrite> pendingWriteQueue =
new ConcurrentLinkedQueue<PendingWrite>();
StreamState(
boolean remoteSideClosed, boolean localSideClosed,
int sendWindowSize, int receiveWindowSize) {
this.remoteSideClosed = remoteSideClosed;
this.localSideClosed = localSideClosed;
this.sendWindowSize = new AtomicInteger(sendWindowSize);
this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
}
boolean isRemoteSideClosed() {
return remoteSideClosed;
}
void closeRemoteSide() {
remoteSideClosed = true;
}
boolean isLocalSideClosed() {
return localSideClosed;
}
void closeLocalSide() {
localSideClosed = true;
}
int getSendWindowSize() {
return sendWindowSize.get();
}
int updateSendWindowSize(int deltaWindowSize) {
return sendWindowSize.addAndGet(deltaWindowSize);
}
int updateReceiveWindowSize(int deltaWindowSize) {
return receiveWindowSize.addAndGet(deltaWindowSize);
}
int getReceiveWindowSizeLowerBound() {
return receiveWindowSizeLowerBound;
}
void setReceiveWindowSizeLowerBound(int receiveWindowSizeLowerBound) {
this.receiveWindowSizeLowerBound = receiveWindowSizeLowerBound;
}
boolean putPendingWrite(PendingWrite msg) {
return pendingWriteQueue.offer(msg);
}
PendingWrite getPendingWrite() {
return pendingWriteQueue.peek();
}
PendingWrite removePendingWrite() {
return pendingWriteQueue.poll();
}
void clearPendingWrites(Throwable cause) {
for (; ; ) {
PendingWrite pendingWrite = pendingWriteQueue.poll();
if (pendingWrite == null) {
break;
}
pendingWrite.fail(cause);
}
}
}
private static final class Node {
private static final Comparator<Node> COMPARATOR = new WeightComparator();
public Node parent; // the dependency of the stream
public int weight; // the weight of the dependency
// Children should be iterator in weighted order
public Set<Node> children = new TreeSet<Node>(COMPARATOR); // the dependents of the stream
public int dependentWeights; // the total weight of all the dependents of the stream
public StreamState state;
public Node(StreamState state) {
this.state = state;
}
public void close() {
this.state = null;
}
public void setWeight(int weight) {
// Remove and re-add parent to maintain comparator order
parent.removeDependent(this);
this.weight = weight;
parent.addDependent(false, this);
}
public void addDependent(boolean exclusive, Node node) {
removeDependent(node);
if (exclusive) {
for (Node child : children) {
node.addDependent(false, child);
}
children.clear();
dependentWeights = 0;
}
children.add(node);
dependentWeights += node.weight;
}
public void removeDependent(Node node) {
if (children.remove(node)) {
dependentWeights -= node.weight;
}
}
}
private static final class WeightComparator implements Comparator<Node> {
@Override
public int compare(Node n1, Node n2) {
return n2.weight - n1.weight;
}
}
public static final class PendingWrite {
public final HttpDataFrame httpDataFrame;
public final ChannelPromise promise;
PendingWrite(HttpDataFrame httpDataFrame, ChannelPromise promise) {
this.httpDataFrame = httpDataFrame;
this.promise = promise;
}
void fail(Throwable cause) {
// httpDataFrame.release();
promise.setFailure(cause);
}
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpConnectionHandler.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.ClosedChannelException;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.internal.EmptyArrays;
import static com.twitter.http2.HttpCodecUtil.HTTP_CONNECTION_STREAM_ID;
import static com.twitter.http2.HttpCodecUtil.isServerId;
/**
* Manages streams within an HTTP/2 connection.
*/
public class HttpConnectionHandler extends ByteToMessageDecoder
implements HttpFrameDecoderDelegate, ChannelOutboundHandler {
private static final HttpProtocolException PROTOCOL_EXCEPTION =
new HttpProtocolException();
static {
PROTOCOL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
}
private static final HttpSettingsFrame SETTINGS_ACK_FRAME =
new DefaultHttpSettingsFrame().setAck(true);
private static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
private static final int DEFAULT_WINDOW_SIZE = 65535;
private int initialSendWindowSize = DEFAULT_WINDOW_SIZE;
private int initialReceiveWindowSize = DEFAULT_WINDOW_SIZE;
private volatile int initialConnectionReceiveWindowSize = DEFAULT_WINDOW_SIZE;
private final HttpConnection httpConnection =
new HttpConnection(initialSendWindowSize, initialReceiveWindowSize);
private int lastStreamId;
private static final int DEFAULT_MAX_CONCURRENT_STREAMS = Integer.MAX_VALUE;
private int remoteConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS;
private int localConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS;
private boolean sentGoAwayFrame;
private boolean receivedGoAwayFrame;
private final ChannelFutureListener connectionErrorListener =
new ConnectionErrorFutureListener();
private ChannelFutureListener closingChannelFutureListener;
private final boolean server;
private final boolean handleStreamWindowUpdates;
private final HttpFrameDecoder httpFrameDecoder;
private final HttpFrameEncoder httpFrameEncoder;
private final HttpHeaderBlockDecoder httpHeaderBlockDecoder;
private final HttpHeaderBlockEncoder httpHeaderBlockEncoder;
private HttpHeaderBlockFrame httpHeaderBlockFrame;
private HttpSettingsFrame httpSettingsFrame;
private boolean needSettingsAck;
private boolean changeDecoderHeaderTableSize;
private int headerTableSize;
private boolean changeEncoderHeaderTableSize;
private int lastHeaderTableSize = Integer.MAX_VALUE;
private int minHeaderTableSize = Integer.MAX_VALUE;
private boolean pushEnabled = true;
private ChannelHandlerContext context;
/**
* Creates a new connection handler.
*
* @param server {@code true} if and only if this connection handler should
* handle the server endpoint of the connection.
* {@code false} if and only if this connection handler should
* handle the client endpoint of the connection.
*/
public HttpConnectionHandler(boolean server) {
this(server, true);
}
/**
* Creates a new connection handler with the specified options.
*/
public HttpConnectionHandler(boolean server, boolean handleStreamWindowUpdates) {
this(server, handleStreamWindowUpdates, 8192, 16384);
}
/**
* Creates a new connection handler with the specified options.
*/
public HttpConnectionHandler(boolean server, int maxChunkSize, int maxHeaderSize) {
this(server, true, maxChunkSize, maxHeaderSize);
}
/**
* Creates a new connection handler with the specified options.
*/
public HttpConnectionHandler(
boolean server, boolean handleStreamWindowUpdates, int maxChunkSize, int maxHeaderSize) {
this.server = server;
this.handleStreamWindowUpdates = handleStreamWindowUpdates;
httpFrameDecoder = new HttpFrameDecoder(server, this, maxChunkSize);
httpFrameEncoder = new HttpFrameEncoder();
httpHeaderBlockDecoder = new HttpHeaderBlockDecoder(maxHeaderSize, DEFAULT_HEADER_TABLE_SIZE);
httpHeaderBlockEncoder = new HttpHeaderBlockEncoder(DEFAULT_HEADER_TABLE_SIZE);
}
public void setConnectionReceiveWindowSize(int connectionReceiveWindowSize) {
if (connectionReceiveWindowSize < 0) {
throw new IllegalArgumentException("connectionReceiveWindowSize");
}
// This will not send a window update frame immediately.
// If this value increases the allowed receive window size,
// a WINDOW_UPDATE frame will be sent when only half of the
// session window size remains during data frame processing.
// If this value decreases the allowed receive window size,
// the window will be reduced as data frames are processed.
initialConnectionReceiveWindowSize = connectionReceiveWindowSize;
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
context = ctx;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
httpFrameDecoder.decode(in);
}
/**
* {@inheritDoc}
*/
@Override
public void readDataFramePadding(int streamId, boolean endStream, int padding) {
int deltaWindowSize = -1 * padding;
int newConnectionWindowSize = httpConnection.updateReceiveWindowSize(
HTTP_CONNECTION_STREAM_ID, deltaWindowSize);
// Check if connection window size is reduced beyond allowable lower bound
if (newConnectionWindowSize < 0) {
issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);
return;
}
// Send a WINDOW_UPDATE frame if less than half the connection window size remains
if (newConnectionWindowSize <= initialConnectionReceiveWindowSize / 2) {
int windowSizeIncrement = initialConnectionReceiveWindowSize - newConnectionWindowSize;
httpConnection.updateReceiveWindowSize(HTTP_CONNECTION_STREAM_ID, windowSizeIncrement);
ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(
HTTP_CONNECTION_STREAM_ID, windowSizeIncrement);
context.writeAndFlush(frame);
}
// Check if we received a DATA frame for a stream which is half-closed (remote) or closed
if (httpConnection.isRemoteSideClosed(streamId)) {
if (streamId <= lastStreamId) {
issueStreamError(streamId, HttpErrorCode.STREAM_CLOSED);
} else if (!sentGoAwayFrame) {
issueStreamError(streamId, HttpErrorCode.PROTOCOL_ERROR);
}
return;
}
// Update receive window size
int newWindowSize = httpConnection.updateReceiveWindowSize(streamId, deltaWindowSize);
// 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 (newWindowSize < httpConnection.getReceiveWindowSizeLowerBound(streamId)) {
issueStreamError(streamId, HttpErrorCode.FLOW_CONTROL_ERROR);
return;
}
// Send a WINDOW_UPDATE frame if less than half the stream window size remains
// Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.
if (handleStreamWindowUpdates && newWindowSize <= initialReceiveWindowSize / 2 && !endStream) {
int windowSizeIncrement = initialReceiveWindowSize - newWindowSize;
httpConnection.updateReceiveWindowSize(streamId, windowSizeIncrement);
ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(streamId, windowSizeIncrement);
context.writeAndFlush(frame);
}
}
/**
* {@inheritDoc}
*/
@Override
public void readDataFrame(int streamId, boolean endStream, boolean endSegment, ByteBuf data) {
// HTTP/2 DATA frame processing requirements:
//
// If an endpoint receives a data frame for a Stream-ID which is not open
// and the endpoint has not sent a GOAWAY frame, it must issue a stream error
// with the error code INVALID_STREAM for the Stream-ID.
//
// If an endpoint receives multiple data frames for invalid Stream-IDs,
// it may close the connection.
//
// If an endpoint refuses a stream it must ignore any data frames for that stream.
//
// If an endpoint receives a data frame after the stream is half-closed (remote)
// or closed, it must respond with a stream error of type STREAM_CLOSED.
int deltaWindowSize = -1 * data.readableBytes();
int newConnectionWindowSize = httpConnection.updateReceiveWindowSize(
HTTP_CONNECTION_STREAM_ID, deltaWindowSize);
// Check if connection window size is reduced beyond allowable lower bound
if (newConnectionWindowSize < 0) {
issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);
return;
}
// Send a WINDOW_UPDATE frame if less than half the connection window size remains
if (newConnectionWindowSize <= initialConnectionReceiveWindowSize / 2) {
int windowSizeIncrement = initialConnectionReceiveWindowSize - newConnectionWindowSize;
httpConnection.updateReceiveWindowSize(HTTP_CONNECTION_STREAM_ID, windowSizeIncrement);
ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(
HTTP_CONNECTION_STREAM_ID, windowSizeIncrement);
context.writeAndFlush(frame);
}
// Check if we received a DATA frame for a stream which is half-closed (remote) or closed
if (httpConnection.isRemoteSideClosed(streamId)) {
if (streamId <= lastStreamId) {
issueStreamError(streamId, HttpErrorCode.STREAM_CLOSED);
} else if (!sentGoAwayFrame) {
issueStreamError(streamId, HttpErrorCode.PROTOCOL_ERROR);
}
return;
}
// Update receive window size
int newWindowSize = httpConnection.updateReceiveWindowSize(streamId, deltaWindowSize);
// 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 (newWindowSize < httpConnection.getReceiveWindowSizeLowerBound(streamId)) {
issueStreamError(streamId, HttpErrorCode.FLOW_CONTROL_ERROR);
return;
}
// Window size became negative due to sender writing frame before receiving SETTINGS
// Send data frames upstream in initialReceiveWindowSize chunks
if (newWindowSize < 0) {
while (data.readableBytes() > initialReceiveWindowSize) {
ByteBuf partialData = data.readBytes(initialReceiveWindowSize);
HttpDataFrame partialDataFrame = new DefaultHttpDataFrame(streamId, partialData);
context.fireChannelRead(partialDataFrame);
}
}
// Send a WINDOW_UPDATE frame if less than half the stream window size remains
// Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.
if (handleStreamWindowUpdates && newWindowSize <= initialReceiveWindowSize / 2 && !endStream) {
int windowSizeIncrement = initialReceiveWindowSize - newWindowSize;
httpConnection.updateReceiveWindowSize(streamId, windowSizeIncrement);
ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(streamId, windowSizeIncrement);
context.writeAndFlush(frame);
}
// Close the remote side of the stream if this is the last frame
if (endStream) {
halfCloseStream(streamId, true, context.channel().newSucceededFuture());
}
HttpDataFrame httpDataFrame = new DefaultHttpDataFrame(streamId, data);
httpDataFrame.setLast(endStream);
context.fireChannelRead(httpDataFrame);
}
/**
* {@inheritDoc}
*/
@Override
public void readHeadersFrame(
int streamId,
boolean endStream,
boolean endSegment,
boolean exclusive,
int dependency,
int weight
) {
// HTTP/2 HEADERS frame processing requirements:
//
// If an endpoint receives a HEADERS frame with a Stream-ID that is less than
// any previously received HEADERS, it must issue a connection error of type
// PROTOCOL_ERROR.
//
// If an endpoint receives multiple SYN_STREAM frames with the same active
// Stream-ID, it must issue a stream error with the status code PROTOCOL_ERROR.
//
// The recipient can reject a stream by sending a stream error with the
// status code REFUSED_STREAM.
if (isRemoteInitiatedId(streamId)) {
if (streamId <= lastStreamId) {
// Check if we received a HEADERS frame for a stream which is half-closed (remote) or closed
if (httpConnection.isRemoteSideClosed(streamId)) {
issueStreamError(streamId, HttpErrorCode.STREAM_CLOSED);
return;
}
} else {
// Try to accept the stream
if (!acceptStream(streamId, exclusive, dependency, weight)) {
issueStreamError(streamId, HttpErrorCode.REFUSED_STREAM);
return;
}
}
} else {
// Check if we received a HEADERS frame for a stream which is half-closed (remote) or closed
if (httpConnection.isRemoteSideClosed(streamId)) {
issueStreamError(streamId, HttpErrorCode.STREAM_CLOSED);
return;
}
}
// Close the remote side of the stream if this is the last frame
if (endStream) {
halfCloseStream(streamId, true, context.channel().newSucceededFuture());
}
HttpHeadersFrame httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);
httpHeadersFrame.setLast(endStream);
httpHeadersFrame.setExclusive(exclusive);
httpHeadersFrame.setDependency(dependency);
httpHeadersFrame.setWeight(weight);
httpHeaderBlockFrame = httpHeadersFrame;
}
/**
* {@inheritDoc}
*/
@Override
public void readPriorityFrame(int streamId, boolean exclusive, int dependency, int weight) {
if (streamId == dependency) {
issueStreamError(streamId, HttpErrorCode.PROTOCOL_ERROR);
} else {
setPriority(streamId, exclusive, dependency, weight);
}
}
/**
* {@inheritDoc}
*/
@Override
public void readRstStreamFrame(int streamId, int errorCode) {
// If a RST_STREAM frame identifying an idle stream is received,
// the recipient MUST treat this as a connection error of type
// PROTOCOL_ERROR.
removeStream(streamId, context.channel().newSucceededFuture());
HttpRstStreamFrame httpRstStreamFrame = new DefaultHttpRstStreamFrame(streamId, errorCode);
context.fireChannelRead(httpRstStreamFrame);
}
/**
* {@inheritDoc}
*/
@Override
public void readSettingsFrame(boolean ack) {
needSettingsAck = !ack;
httpSettingsFrame = new DefaultHttpSettingsFrame();
httpSettingsFrame.setAck(ack);
if (ack && changeDecoderHeaderTableSize) {
httpHeaderBlockDecoder.setMaxHeaderTableSize(headerTableSize);
changeDecoderHeaderTableSize = false;
}
}
/**
* {@inheritDoc}
*/
@Override
public void readSetting(int id, int value) {
httpSettingsFrame.setValue(id, value);
switch (id) {
case HttpSettingsFrame.SETTINGS_HEADER_TABLE_SIZE:
// Ignore 'negative' values -- they are too large for java
if (value >= 0) {
changeEncoderHeaderTableSize = true;
lastHeaderTableSize = value;
if (lastHeaderTableSize < minHeaderTableSize) {
minHeaderTableSize = lastHeaderTableSize;
}
}
break;
case HttpSettingsFrame.SETTINGS_ENABLE_PUSH:
if (value == 0) {
pushEnabled = false;
} else if (value == 1) {
pushEnabled = true;
} else {
issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);
}
break;
case HttpSettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS:
if (value >= 0) {
remoteConcurrentStreams = value;
}
break;
case HttpSettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE:
if (value >= 0) {
updateInitialSendWindowSize(value);
} else {
issueConnectionError(HttpErrorCode.FLOW_CONTROL_ERROR);
}
break;
case HttpSettingsFrame.SETTINGS_MAX_FRAME_SIZE:
if (value != HttpCodecUtil.HTTP_MAX_LENGTH) {
issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);
}
break;
default:
// Ignore Unknown Settings
}
}
/**
* {@inheritDoc}
*/
@Override
public void readSettingsEnd() {
if (changeEncoderHeaderTableSize) {
synchronized (httpHeaderBlockEncoder) {
httpHeaderBlockEncoder.setDecoderMaxHeaderTableSize(minHeaderTableSize);
httpHeaderBlockEncoder.setDecoderMaxHeaderTableSize(lastHeaderTableSize);
// Writes of settings ack must occur in order
ByteBuf frame = httpFrameEncoder.encodeSettingsFrame(SETTINGS_ACK_FRAME);
context.writeAndFlush(frame);
}
changeEncoderHeaderTableSize = false;
lastHeaderTableSize = Integer.MAX_VALUE;
minHeaderTableSize = Integer.MAX_VALUE;
} else if (needSettingsAck) {
ByteBuf frame = httpFrameEncoder.encodeSettingsFrame(SETTINGS_ACK_FRAME);
context.writeAndFlush(frame);
}
Object frame = httpSettingsFrame;
httpSettingsFrame = null;
context.fireChannelRead(frame);
}
/**
* {@inheritDoc}
*/
@Override
public void readPushPromiseFrame(int streamId, int promisedStreamId) {
// TODO(jpinner) handle push promise frames
// Any we receive must be associated with a "peer-initiated" stream.
// Since we don't have a way currently to initiate streams, any
// frame that we receive must be treated as a protocol error.
issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);
}
/**
* {@inheritDoc}
*/
@Override
public void readPingFrame(long data, boolean ack) {
// HTTP/2 PING frame processing requirements:
//
// Receivers of a PING frame should send an identical frame to the sender
// as soon as possible.
//
// Receivers of a PING frame must ignore frames that it did not initiate
HttpPingFrame httpPingFrame = new DefaultHttpPingFrame(data);
httpPingFrame.setPong(true);
if (ack) {
context.fireChannelRead(httpPingFrame);
} else {
ByteBuf frame = httpFrameEncoder.encodePingFrame(data, false);
context.writeAndFlush(frame);
}
}
/**
* {@inheritDoc}
*/
@Override
public void readGoAwayFrame(int lastStreamId, int errorCode) {
receivedGoAwayFrame = true;
HttpGoAwayFrame httpGoAwayFrame = new DefaultHttpGoAwayFrame(lastStreamId, errorCode);
context.fireChannelRead(httpGoAwayFrame);
}
/**
* {@inheritDoc}
*/
@Override
public void readWindowUpdateFrame(int streamId, int windowSizeIncrement) {
// HTTP/2 WINDOW_UPDATE frame processing requirements:
//
// Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31
// must send a RST_STREAM with the status code FLOW_CONTROL_ERROR.
//
// Sender should ignore all WINDOW_UPDATE frames associated with a stream
// after sending the last frame for the stream.
// Ignore frames for half-closed streams
if (streamId != HTTP_CONNECTION_STREAM_ID && httpConnection.isLocalSideClosed(streamId)) {
return;
}
// Check for numerical overflow
if (httpConnection.getSendWindowSize(streamId) > Integer.MAX_VALUE - windowSizeIncrement) {
if (streamId == HTTP_CONNECTION_STREAM_ID) {
issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);
} else {
issueStreamError(streamId, HttpErrorCode.FLOW_CONTROL_ERROR);
}
return;
}
updateSendWindowSize(context, streamId, windowSizeIncrement);
}
/**
* {@inheritDoc}
*/
@Override
public void readHeaderBlock(ByteBuf headerBlockFragment) {
try {
httpHeaderBlockDecoder.decode(headerBlockFragment, httpHeaderBlockFrame);
} catch (IOException e) {
httpHeaderBlockFrame = null;
issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);
}
}
/**
* {@inheritDoc}
*/
@Override
public void readHeaderBlockEnd() {
httpHeaderBlockDecoder.endHeaderBlock(httpHeaderBlockFrame);
if (httpHeaderBlockFrame == null) {
return;
}
// Check if we received a valid Header Block
if (httpHeaderBlockFrame.isInvalid()) {
issueStreamError(httpHeaderBlockFrame.getStreamId(), HttpErrorCode.PROTOCOL_ERROR);
return;
}
Object frame = httpHeaderBlockFrame;
httpHeaderBlockFrame = null;
context.fireChannelRead(frame);
}
/**
* {@inheritDoc}
*/
@Override
public void readFrameError(String message) {
issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);
}
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
ctx.bind(localAddress, promise);
}
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress,
SocketAddress localAddress,
ChannelPromise promise
) throws Exception {
ctx.connect(remoteAddress, localAddress, promise);
}
@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
// HTTP/2 connection requirements:
//
// When either endpoint closes the transport-level connection,
// it must first send a GOAWAY frame.
//
// Avoid NotYetConnectedException
if (!ctx.channel().isActive()) {
ctx.disconnect(promise);
} else {
sendGoAwayFrame(ctx, promise);
}
}
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
// HTTP/2 connection requirements:
//
// When either endpoint closes the transport-level connection,
// it must first send a GOAWAY frame.
//
// Avoid NotYetConnectedException
if (!ctx.channel().isActive()) {
ctx.close(promise);
} else {
sendGoAwayFrame(ctx, promise);
}
}
@Override
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
ctx.deregister(promise);
}
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
ctx.read();
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
if (msg instanceof HttpDataFrame) {
HttpDataFrame httpDataFrame = (HttpDataFrame) msg;
int streamId = httpDataFrame.getStreamId();
// Frames must not be sent on half-closed streams
if (httpConnection.isLocalSideClosed(streamId)) {
promise.setFailure(PROTOCOL_EXCEPTION);
return;
}
// HTTP/2 DATA frame flow control processing requirements:
//
// Sender must not send a data frame with data length greater
// than the transfer window size.
//
// After sending each data frame, the sender decrements its
// transfer window size by the amount of data transmitted.
//
// When the window size becomes less than or equal to 0, the
// sender must pause transmitting data frames.
int dataLength = httpDataFrame.content().readableBytes();
int sendWindowSize = httpConnection.getSendWindowSize(streamId);
int connectionSendWindowSize = httpConnection.getSendWindowSize(
HTTP_CONNECTION_STREAM_ID);
sendWindowSize = Math.min(sendWindowSize, connectionSendWindowSize);
if (sendWindowSize <= 0) {
// Stream is stalled -- enqueue Data frame and return
httpConnection.putPendingWrite(
streamId, new HttpConnection.PendingWrite(httpDataFrame, promise));
return;
} else if (sendWindowSize < dataLength) {
// Stream is not stalled but we cannot send the entire frame
httpConnection.updateSendWindowSize(streamId, -1 * sendWindowSize);
httpConnection.updateSendWindowSize(HTTP_CONNECTION_STREAM_ID, -1 * sendWindowSize);
// Create a partial data frame whose length is the current window size
ByteBuf data = httpDataFrame.content().readSlice(sendWindowSize).retain();
ByteBuf partialDataFrame = httpFrameEncoder.encodeDataFrame(streamId, false, data);
// Enqueue the remaining data (will be the first frame queued)
httpConnection.putPendingWrite(
streamId, new HttpConnection.PendingWrite(httpDataFrame, promise));
ChannelPromise writeFuture = ctx.channel().newPromise();
// The transfer window size is pre-decremented when sending a data frame downstream.
// Close the connection on write failures that leaves the transfer window in a corrupt state.
writeFuture.addListener(connectionErrorListener);
ctx.write(partialDataFrame, writeFuture);
return;
} else {
// Window size is large enough to send entire data frame
httpConnection.updateSendWindowSize(streamId, -1 * dataLength);
httpConnection.updateSendWindowSize(HTTP_CONNECTION_STREAM_ID, -1 * dataLength);
// The transfer window size is pre-decremented when sending a data frame downstream.
// Close the connection on write failures that leaves the transfer window in a corrupt state.
promise.addListener(connectionErrorListener);
}
// Close the local side of the stream if this is the last frame
if (httpDataFrame.isLast()) {
halfCloseStream(streamId, false, promise);
}
ByteBuf frame = httpFrameEncoder.encodeDataFrame(
streamId,
httpDataFrame.isLast(),
httpDataFrame.content()
);
ctx.write(frame, promise);
} else if (msg instanceof HttpHeadersFrame) {
HttpHeadersFrame httpHeadersFrame = (HttpHeadersFrame) msg;
int streamId = httpHeadersFrame.getStreamId();
if (isRemoteInitiatedId(streamId)) {
if (streamId <= lastStreamId) {
// Attempting to send headers for an older stream
// (older than the latest accepted remote initiated stream)
// Ensure that the frames are not sent on a half-closed (local) or closed streams
if (httpConnection.isLocalSideClosed(streamId)) {
promise.setFailure(PROTOCOL_EXCEPTION);
return;
}
} else {
// If we are attempting to write to a remote initiated stream id which is greater than the latest
// accepted stream Id then we must throw a protocol exception! i.e we cannot write on a remote
// initiated stream which we have not accepted before
promise.setFailure(PROTOCOL_EXCEPTION);
return;
}
} else {
// This is a locally initiated stream (Push)
boolean exclusive = httpHeadersFrame.isExclusive();
int dependency = httpHeadersFrame.getDependency();
int weight = httpHeadersFrame.getWeight();
if (!acceptStream(streamId, exclusive, dependency, weight)) {
promise.setFailure(PROTOCOL_EXCEPTION);
return;
}
}
// Close the local side of the stream if this is the last frame
if (httpHeadersFrame.isLast()) {
halfCloseStream(streamId, false, promise);
}
synchronized (httpHeaderBlockEncoder) {
ByteBuf frame = httpFrameEncoder.encodeHeadersFrame(
httpHeadersFrame.getStreamId(),
httpHeadersFrame.isLast(),
httpHeadersFrame.isExclusive(),
httpHeadersFrame.getDependency(),
httpHeadersFrame.getWeight(),
httpHeaderBlockEncoder.encode(ctx, httpHeadersFrame)
);
// Writes of compressed data must occur in order
ctx.write(frame, promise);
}
} else if (msg instanceof HttpPriorityFrame) {
HttpPriorityFrame httpPriorityFrame = (HttpPriorityFrame) msg;
int streamId = httpPriorityFrame.getStreamId();
boolean exclusive = httpPriorityFrame.isExclusive();
int dependency = httpPriorityFrame.getDependency();
int weight = httpPriorityFrame.getWeight();
setPriority(streamId, exclusive, dependency, weight);
ByteBuf frame = httpFrameEncoder.encodePriorityFrame(
streamId,
exclusive,
dependency,
weight
);
ctx.write(frame, promise);
} else if (msg instanceof HttpRstStreamFrame) {
HttpRstStreamFrame httpRstStreamFrame = (HttpRstStreamFrame) msg;
removeStream(httpRstStreamFrame.getStreamId(), promise);
ByteBuf frame = httpFrameEncoder.encodeRstStreamFrame(
httpRstStreamFrame.getStreamId(),
httpRstStreamFrame.getErrorCode().getCode());
ctx.write(frame, promise);
} else if (msg instanceof HttpSettingsFrame) {
// TODO(jpinner) currently cannot have more than one settings frame outstanding at a time
HttpSettingsFrame httpSettingsFrame = (HttpSettingsFrame) msg;
if (httpSettingsFrame.isAck()) {
// Cannot send an acknowledgement frame
promise.setFailure(PROTOCOL_EXCEPTION);
return;
}
int newHeaderTableSize =
httpSettingsFrame.getValue(HttpSettingsFrame.SETTINGS_HEADER_TABLE_SIZE);
if (newHeaderTableSize >= 0) {
headerTableSize = newHeaderTableSize;
changeDecoderHeaderTableSize = true;
}
int newConcurrentStreams =
httpSettingsFrame.getValue(HttpSettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
if (newConcurrentStreams >= 0) {
localConcurrentStreams = newConcurrentStreams;
}
int newInitialWindowSize =
httpSettingsFrame.getValue(HttpSettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
if (newInitialWindowSize >= 0) {
updateInitialReceiveWindowSize(newInitialWindowSize);
}
ByteBuf frame = httpFrameEncoder.encodeSettingsFrame(httpSettingsFrame);
ctx.write(frame, promise);
} else if (msg instanceof HttpPushPromiseFrame) {
if (!pushEnabled) {
promise.setFailure(PROTOCOL_EXCEPTION);
return;
}
synchronized (httpHeaderBlockEncoder) {
HttpPushPromiseFrame httpPushPromiseFrame = (HttpPushPromiseFrame) msg;
ByteBuf frame = httpFrameEncoder.encodePushPromiseFrame(
httpPushPromiseFrame.getStreamId(),
httpPushPromiseFrame.getPromisedStreamId(),
httpHeaderBlockEncoder.encode(ctx, httpPushPromiseFrame)
);
// Writes of compressed data must occur in order
ctx.write(frame, promise);
}
} else if (msg instanceof HttpPingFrame) {
HttpPingFrame httpPingFrame = (HttpPingFrame) msg;
if (httpPingFrame.isPong()) {
// Cannot send a PONG frame
promise.setFailure(PROTOCOL_EXCEPTION);
} else {
ByteBuf frame = httpFrameEncoder.encodePingFrame(httpPingFrame.getData(), false);
ctx.write(frame, promise);
}
} else if (msg instanceof HttpGoAwayFrame) {
// Why is this being sent? Intercept it and fail the write.
// Should have sent a CLOSE ChannelStateEvent
promise.setFailure(PROTOCOL_EXCEPTION);
} else if (msg instanceof HttpWindowUpdateFrame) {
HttpWindowUpdateFrame httpWindowUpdateFrame = (HttpWindowUpdateFrame) msg;
int streamId = httpWindowUpdateFrame.getStreamId();
if (handleStreamWindowUpdates || streamId == HTTP_CONNECTION_STREAM_ID) {
// Why is this being sent? Intercept it and fail the write.
promise.setFailure(PROTOCOL_EXCEPTION);
} else {
int windowSizeIncrement = httpWindowUpdateFrame.getWindowSizeIncrement();
httpConnection.updateReceiveWindowSize(streamId, windowSizeIncrement);
ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(streamId, windowSizeIncrement);
ctx.write(frame, promise);
}
} else {
ctx.write(msg, promise);
}
}
@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
// HTTP/2 Connection Error Handling:
//
// When a connection error occurs, the endpoint encountering the error must first
// send a GOAWAY frame with the stream identifier of the most recently received stream
// from the remote endpoint, and the error code for why the connection is terminating.
//
// After sending the GOAWAY frame, the endpoint must close the TCP connection.
private void issueConnectionError(HttpErrorCode status) {
ChannelFuture future = sendGoAwayFrame(status);
future.addListener(ChannelFutureListener.CLOSE);
}
// Http/2 Stream Error Handling:
//
// Upon a stream error, the endpoint must send a RST_STREAM frame which contains
// the Stream-ID for the stream where the error occurred and the error status which
// caused the error.
//
// After sending the RST_STREAM, the stream is closed to the sending endpoint.
//
// Note: this is only called by the worker thread
private void issueStreamError(int streamId, HttpErrorCode errorCode) {
boolean fireMessageReceived = !httpConnection.isRemoteSideClosed(streamId);
ChannelPromise promise = context.channel().newPromise();
removeStream(streamId, promise);
ByteBuf frame = httpFrameEncoder.encodeRstStreamFrame(streamId, errorCode.getCode());
context.writeAndFlush(frame, promise);
if (fireMessageReceived) {
HttpRstStreamFrame httpRstStreamFrame = new DefaultHttpRstStreamFrame(streamId, errorCode);
context.fireChannelRead(httpRstStreamFrame);
}
}
// Helper functions
private boolean isRemoteInitiatedId(int id) {
boolean serverId = isServerId(id);
return server && !serverId || !server && serverId;
}
// need to synchronize to prevent new streams from being created while updating active streams
private synchronized void updateInitialSendWindowSize(int newInitialWindowSize) {
int deltaWindowSize = newInitialWindowSize - initialSendWindowSize;
initialSendWindowSize = newInitialWindowSize;
httpConnection.updateAllSendWindowSizes(deltaWindowSize);
}
// need to synchronize to prevent new streams from being created while updating active streams
private synchronized void updateInitialReceiveWindowSize(int newInitialWindowSize) {
int deltaWindowSize = newInitialWindowSize - initialReceiveWindowSize;
initialReceiveWindowSize = newInitialWindowSize;
httpConnection.updateAllReceiveWindowSizes(deltaWindowSize);
}
// need to synchronize accesses to sentGoAwayFrame, lastStreamId, and initial window sizes
private synchronized boolean acceptStream(
int streamId, boolean exclusive, int dependency, int weight) {
// Cannot initiate any new streams after receiving or sending GOAWAY
if (receivedGoAwayFrame || sentGoAwayFrame) {
return false;
}
boolean remote = isRemoteInitiatedId(streamId);
int maxConcurrentStreams = remote ? localConcurrentStreams : remoteConcurrentStreams;
if (httpConnection.numActiveStreams(remote) >= maxConcurrentStreams) {
return false;
}
httpConnection.acceptStream(
streamId, false, false, initialSendWindowSize, initialReceiveWindowSize, remote);
if (remote) {
lastStreamId = streamId;
}
setPriority(streamId, exclusive, dependency, weight);
return true;
}
private synchronized boolean setPriority(
int streamId, boolean exclusive, int dependency, int weight) {
return httpConnection.setPriority(streamId, exclusive, dependency, weight);
}
private void halfCloseStream(int streamId, boolean remote, ChannelFuture future) {
if (remote) {
httpConnection.closeRemoteSide(streamId, isRemoteInitiatedId(streamId));
} else {
httpConnection.closeLocalSide(streamId, isRemoteInitiatedId(streamId));
}
if (closingChannelFutureListener != null && httpConnection.noActiveStreams()) {
future.addListener(closingChannelFutureListener);
}
}
private void removeStream(int streamId, ChannelFuture future) {
httpConnection.removeStream(streamId, isRemoteInitiatedId(streamId));
if (closingChannelFutureListener != null && httpConnection.noActiveStreams()) {
future.addListener(closingChannelFutureListener);
}
}
private void updateSendWindowSize(
ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {
httpConnection.updateSendWindowSize(streamId, windowSizeIncrement);
while (true) {
// Check if we have unblocked a stalled stream
HttpConnection.PendingWrite e = httpConnection.getPendingWrite(streamId);
if (e == null) {
break;
}
HttpDataFrame httpDataFrame = e.httpDataFrame;
int dataFrameSize = httpDataFrame.content().readableBytes();
int writeStreamId = httpDataFrame.getStreamId();
int sendWindowSize = httpConnection.getSendWindowSize(writeStreamId);
int connectionSendWindowSize = httpConnection.getSendWindowSize(
HTTP_CONNECTION_STREAM_ID);
sendWindowSize = Math.min(sendWindowSize, connectionSendWindowSize);
if (sendWindowSize <= 0) {
return;
} else if (sendWindowSize < dataFrameSize) {
// We can send a partial frame
httpConnection.updateSendWindowSize(writeStreamId, -1 * sendWindowSize);
httpConnection.updateSendWindowSize(HTTP_CONNECTION_STREAM_ID, -1 * sendWindowSize);
// Create a partial data frame whose length is the current window size
ByteBuf data = httpDataFrame.content().readSlice(sendWindowSize).retain();
ByteBuf partialDataFrame = httpFrameEncoder.encodeDataFrame(writeStreamId, false, data);
ChannelPromise writeFuture = ctx.channel().newPromise();
// The transfer window size is pre-decremented when sending a data frame downstream.
// Close the connection on write failures that leaves the transfer window in a corrupt state.
writeFuture.addListener(connectionErrorListener);
ctx.writeAndFlush(partialDataFrame, writeFuture);
} else {
// Window size is large enough to send entire data frame
httpConnection.removePendingWrite(writeStreamId);
httpConnection.updateSendWindowSize(writeStreamId, -1 * dataFrameSize);
httpConnection.updateSendWindowSize(HTTP_CONNECTION_STREAM_ID, -1 * dataFrameSize);
// The transfer window size is pre-decremented when sending a data frame downstream.
// Close the connection on write failures that leaves the transfer window in a corrupt state.
e.promise.addListener(connectionErrorListener);
// Close the local side of the stream if this is the last frame
if (httpDataFrame.isLast()) {
halfCloseStream(writeStreamId, false, e.promise);
}
ByteBuf frame = httpFrameEncoder.encodeDataFrame(
writeStreamId,
httpDataFrame.isLast(),
httpDataFrame.content()
);
ctx.writeAndFlush(frame, e.promise);
}
}
}
private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelPromise promise) {
ChannelFuture future = sendGoAwayFrame(HttpErrorCode.NO_ERROR);
if (httpConnection.noActiveStreams()) {
future.addListener(new ClosingChannelFutureListener(ctx, promise));
} else {
closingChannelFutureListener = new ClosingChannelFutureListener(ctx, promise);
}
}
private synchronized ChannelFuture sendGoAwayFrame(HttpErrorCode httpErrorCode) {
if (!sentGoAwayFrame) {
sentGoAwayFrame = true;
ChannelPromise promise = context.channel().newPromise();
ByteBuf frame = httpFrameEncoder.encodeGoAwayFrame(lastStreamId, httpErrorCode.getCode());
context.writeAndFlush(frame, promise);
return promise;
}
return context.channel().newSucceededFuture();
}
private final class ConnectionErrorFutureListener implements ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
issueConnectionError(HttpErrorCode.INTERNAL_ERROR);
}
}
}
private static final class ClosingChannelFutureListener implements ChannelFutureListener {
private final ChannelHandlerContext ctx;
private final ChannelPromise promise;
ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelPromise promise) {
this.ctx = ctx;
this.promise = promise;
}
public void operationComplete(ChannelFuture sentGoAwayFuture) throws Exception {
if (!(sentGoAwayFuture.cause() instanceof ClosedChannelException)) {
ctx.close(promise);
} else {
promise.setSuccess();
}
}
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpDataFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
/**
* An HTTP/2 DATA Frame
*/
public interface HttpDataFrame extends ByteBufHolder, HttpStreamFrame {
/**
* Returns {@code true} if this frame is the last frame to be transmitted on the stream.
*/
boolean isLast();
/**
* Sets if this frame is the last frame to be transmitted on the stream.
*/
HttpDataFrame setLast(boolean last);
@Override
HttpDataFrame setStreamId(int streamId);
/**
* Returns the data payload of this frame. If there is no data payload
* {@link io.netty.buffer.Unpooled#EMPTY_BUFFER} is returned.
* <p/>
* The data payload cannot exceed 16384 bytes.
*/
@Override
ByteBuf content();
@Override
HttpDataFrame copy();
@Override
HttpDataFrame duplicate();
@Override
HttpDataFrame retain();
@Override
HttpDataFrame retain(int increment);
}
================================================
FILE: src/main/java/com/twitter/http2/HttpErrorCode.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
public class HttpErrorCode implements Comparable<HttpErrorCode> {
/**
* 0 No Error
*/
public static final HttpErrorCode NO_ERROR =
new HttpErrorCode(0, "NO_ERROR");
/**
* 1 Protocol Error
*/
public static final HttpErrorCode PROTOCOL_ERROR =
new HttpErrorCode(1, "PROTOCOL_ERROR");
/**
* 2 Internal Error
*/
public static final HttpErrorCode INTERNAL_ERROR =
new HttpErrorCode(2, "INTERNAL_ERROR");
/**
* 3 Flow Control Error
*/
public static final HttpErrorCode FLOW_CONTROL_ERROR =
new HttpErrorCode(3, "FLOW_CONTROL_ERROR");
/**
* 4 Settings Timeout
*/
public static final HttpErrorCode SETTINGS_TIMEOUT =
new HttpErrorCode(4, "SETTINGS_TIMEOUT");
/**
* 5 Stream Closed
*/
public static final HttpErrorCode STREAM_CLOSED =
new HttpErrorCode(5, "STREAM_CLOSED");
/**
* 6 Frame Size Error
*/
public static final HttpErrorCode FRAME_SIZE_ERROR =
new HttpErrorCode(6, "FRAME_SIZE_ERROR");
/**
* 7 Refused Stream
*/
public static final HttpErrorCode REFUSED_STREAM =
new HttpErrorCode(7, "REFUSED_STREAM");
/**
* 8 Cancel
*/
public static final HttpErrorCode CANCEL =
new HttpErrorCode(8, "CANCEL");
/**
* 9 Compression Error
*/
public static final HttpErrorCode COMPRESSION_ERROR =
new HttpErrorCode(9, "COMPRESSION_ERROR");
/**
* 10 Connect Error
*/
public static final HttpErrorCode CONNECT_ERROR =
new HttpErrorCode(10, "CONNECT_ERROR");
/**
* 11 Enhance Your Calm (420)
*/
public static final HttpErrorCode ENHANCE_YOUR_CALM =
new HttpErrorCode(420, "ENHANCE_YOUR_CALM");
/**
* 12 Inadequate Security
*/
public static final HttpErrorCode INADEQUATE_SECURITY =
new HttpErrorCode(12, "INADEQUATE_SECURITY");
/**
* Returns the {@link HttpErrorCode} represented by the specified code.
* If the specified code is a defined HTTP error code, a cached instance
* will be returned. Otherwise, a new instance will be returned.
*/
public static HttpErrorCode valueOf(int code) {
switch (code) {
case 0:
return NO_ERROR;
case 1:
return PROTOCOL_ERROR;
case 2:
return INTERNAL_ERROR;
case 3:
return FLOW_CONTROL_ERROR;
case 4:
return SETTINGS_TIMEOUT;
case 5:
return STREAM_CLOSED;
case 6:
return FRAME_SIZE_ERROR;
case 7:
return REFUSED_STREAM;
case 8:
return CANCEL;
case 9:
return COMPRESSION_ERROR;
case 10:
return CONNECT_ERROR;
case 11:
return ENHANCE_YOUR_CALM;
case 12:
return INADEQUATE_SECURITY;
case 420:
return ENHANCE_YOUR_CALM;
default:
return new HttpErrorCode(code, "UNKNOWN (" + code + ')');
}
}
private final int code;
private final String statusPhrase;
/**
* Creates a new instance with the specified {@code code} and its
* {@code statusPhrase}.
*/
public HttpErrorCode(int code, String statusPhrase) {
if (statusPhrase == null) {
throw new NullPointerException("statusPhrase");
}
this.code = code;
this.statusPhrase = statusPhrase;
}
/**
* Returns the code of this status.
*/
public int getCode() {
return code;
}
/**
* Returns the status phrase of this status.
*/
public String getStatusPhrase() {
return statusPhrase;
}
@Override
public int hashCode() {
return getCode();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof HttpErrorCode)) {
return false;
}
return getCode() == ((HttpErrorCode) o).getCode();
}
@Override
public String toString() {
return getStatusPhrase();
}
@Override
public int compareTo(HttpErrorCode o) {
return getCode() - o.getCode();
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
/**
* An HTTP/2 Frame
*/
public interface HttpFrame {
// Tag interface
}
================================================
FILE: src/main/java/com/twitter/http2/HttpFrameDecoder.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.nio.charset.StandardCharsets;
import io.netty.buffer.ByteBuf;
import static com.twitter.http2.HttpCodecUtil.HTTP_CONTINUATION_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_DATA_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_ACK;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_HEADERS;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_SEGMENT;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_STREAM;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_PADDED;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_PRIORITY;
import static com.twitter.http2.HttpCodecUtil.HTTP_FRAME_HEADER_SIZE;
import static com.twitter.http2.HttpCodecUtil.HTTP_GOAWAY_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_HEADERS_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_MAX_LENGTH;
import static com.twitter.http2.HttpCodecUtil.HTTP_PING_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_PRIORITY_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_PUSH_PROMISE_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_RST_STREAM_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_SETTINGS_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_WINDOW_UPDATE_FRAME;
import static com.twitter.http2.HttpCodecUtil.getSignedInt;
import static com.twitter.http2.HttpCodecUtil.getSignedLong;
import static com.twitter.http2.HttpCodecUtil.getUnsignedInt;
import static com.twitter.http2.HttpCodecUtil.getUnsignedMedium;
import static com.twitter.http2.HttpCodecUtil.getUnsignedShort;
/**
* Decodes {@link ByteBuf}s into HTTP/2 Frames.
*/
public class HttpFrameDecoder {
private static final byte[] CLIENT_CONNECTION_PREFACE =
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII);
private final int maxChunkSize;
private final HttpFrameDecoderDelegate delegate;
private State state;
// HTTP/2 frame header fields
private int length;
private short type;
private byte flags;
private int streamId;
// HTTP/2 frame padding length
private int paddingLength;
private enum State {
READ_CONNECTION_HEADER,
READ_FRAME_HEADER,
READ_PADDING_LENGTH,
READ_DATA_FRAME,
READ_DATA,
READ_HEADERS_FRAME,
READ_PRIORITY_FRAME,
READ_RST_STREAM_FRAME,
READ_SETTINGS_FRAME,
READ_SETTING,
READ_PUSH_PROMISE_FRAME,
READ_PING_FRAME,
READ_GOAWAY_FRAME,
READ_WINDOW_UPDATE_FRAME,
READ_CONTINUATION_FRAME_HEADER,
READ_HEADER_BLOCK,
SKIP_FRAME_PADDING,
SKIP_FRAME_PADDING_CONTINUATION,
FRAME_ERROR
}
/**
* Creates a new instance with the specified @{code HttpFrameDecoderDelegate}
* and the default {@code maxChunkSize (8192)}.
*/
public HttpFrameDecoder(boolean server, HttpFrameDecoderDelegate delegate) {
this(server, delegate, 8192);
}
/**
* Creates a new instance with the specified parameters.
*/
public HttpFrameDecoder(boolean server, HttpFrameDecoderDelegate delegate, int maxChunkSize) {
if (delegate == null) {
throw new NullPointerException("delegate");
}
if (maxChunkSize <= 0) {
throw new IllegalArgumentException(
"maxChunkSize must be a positive integer: " + maxChunkSize);
}
this.delegate = delegate;
this.maxChunkSize = maxChunkSize;
if (server) {
state = State.READ_CONNECTION_HEADER;
} else {
state = State.READ_FRAME_HEADER;
}
}
/**
* Decode the byte buffer.
*/
public void decode(ByteBuf buffer) {
boolean endStream;
boolean endSegment;
int minLength;
int dependency;
int weight;
boolean exclusive;
int errorCode;
while (true) {
switch (state) {
case READ_CONNECTION_HEADER:
while (buffer.isReadable()) {
byte b = buffer.readByte();
if (b != CLIENT_CONNECTION_PREFACE[length++]) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid Connection Header");
return;
}
if (length == CLIENT_CONNECTION_PREFACE.length) {
state = State.READ_FRAME_HEADER;
break;
}
}
if (buffer.isReadable()) {
break;
} else {
return;
}
case READ_FRAME_HEADER:
// Wait until entire header is readable
if (buffer.readableBytes() < HTTP_FRAME_HEADER_SIZE) {
return;
}
// Read frame header fields
readFrameHeader(buffer);
// TODO(jpinner) Sec 4.2 FRAME_SIZE_ERROR
if (!isValidFrameHeader(length, type, flags, streamId)) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid Frame Header");
} else if (frameHasPadding(type, flags)) {
state = State.READ_PADDING_LENGTH;
} else {
paddingLength = 0;
state = getNextState(length, type);
}
break;
case READ_PADDING_LENGTH:
if (buffer.readableBytes() < 1) {
return;
}
paddingLength = buffer.readUnsignedByte();
--length;
if (!isValidPaddingLength(length, type, flags, paddingLength)) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid Frame Padding Length");
} else {
state = getNextState(length, type);
}
break;
case READ_DATA_FRAME:
endStream = hasFlag(flags, HTTP_FLAG_END_STREAM);
state = State.READ_DATA;
if (hasFlag(flags, HTTP_FLAG_PADDED)) {
delegate.readDataFramePadding(streamId, endStream, paddingLength + 1);
}
break;
case READ_DATA:
// Generate data frames that do not exceed maxChunkSize
// maxChunkSize must be > 0 so we cannot infinitely loop
int dataLength = Math.min(maxChunkSize, length - paddingLength);
// Wait until entire frame is readable
if (buffer.readableBytes() < dataLength) {
return;
}
ByteBuf data = buffer.readBytes(dataLength);
length -= dataLength;
if (length == paddingLength) {
if (paddingLength == 0) {
state = State.READ_FRAME_HEADER;
} else {
state = State.SKIP_FRAME_PADDING;
}
}
endStream = length == paddingLength && hasFlag(flags, HTTP_FLAG_END_STREAM);
endSegment = length == paddingLength && hasFlag(flags, HTTP_FLAG_END_SEGMENT);
delegate.readDataFrame(streamId, endStream, endSegment, data);
break;
case READ_HEADERS_FRAME:
minLength = 0;
if (hasFlag(flags, HTTP_FLAG_PRIORITY)) {
minLength = 5;
}
if (buffer.readableBytes() < minLength) {
return;
}
endStream = hasFlag(flags, HTTP_FLAG_END_STREAM);
endSegment = hasFlag(flags, HTTP_FLAG_END_SEGMENT);
exclusive = false;
dependency = 0;
weight = 16;
if (hasFlag(flags, HTTP_FLAG_PRIORITY)) {
dependency = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
weight = buffer.readUnsignedByte() + 1;
if (dependency < 0) {
dependency = dependency & 0x7FFFFFFF;
exclusive = true;
}
length -= 5;
}
state = State.READ_HEADER_BLOCK;
delegate.readHeadersFrame(streamId, endStream, endSegment, exclusive, dependency, weight);
break;
case READ_PRIORITY_FRAME:
if (buffer.readableBytes() < length) {
return;
}
exclusive = false;
dependency = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
weight = buffer.readUnsignedByte() + 1;
if (dependency < 0) {
dependency = dependency & 0x7FFFFFFF;
exclusive = true;
}
state = State.READ_FRAME_HEADER;
delegate.readPriorityFrame(streamId, exclusive, dependency, weight);
break;
case READ_RST_STREAM_FRAME:
if (buffer.readableBytes() < length) {
return;
}
errorCode = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(length);
state = State.READ_FRAME_HEADER;
delegate.readRstStreamFrame(streamId, errorCode);
break;
case READ_SETTINGS_FRAME:
boolean ack = hasFlag(flags, HTTP_FLAG_ACK);
state = State.READ_SETTING;
delegate.readSettingsFrame(ack);
break;
case READ_SETTING:
if (length == 0) {
state = State.READ_FRAME_HEADER;
delegate.readSettingsEnd();
break;
}
if (buffer.readableBytes() < 6) {
return;
}
int id = getUnsignedShort(buffer, buffer.readerIndex());
int value = getSignedInt(buffer, buffer.readerIndex() + 2);
buffer.skipBytes(6);
length -= 6;
delegate.readSetting(id, value);
break;
case READ_PUSH_PROMISE_FRAME:
if (buffer.readableBytes() < 4) {
return;
}
int promisedStreamId = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
length -= 4;
if (promisedStreamId == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid Promised-Stream-ID");
} else {
state = State.READ_HEADER_BLOCK;
delegate.readPushPromiseFrame(streamId, promisedStreamId);
}
break;
case READ_PING_FRAME:
if (buffer.readableBytes() < length) {
return;
}
long ping = getSignedLong(buffer, buffer.readerIndex());
buffer.skipBytes(length);
boolean pong = hasFlag(flags, HTTP_FLAG_ACK);
state = State.READ_FRAME_HEADER;
delegate.readPingFrame(ping, pong);
break;
case READ_GOAWAY_FRAME:
if (buffer.readableBytes() < 8) {
return;
}
int lastStreamId = getUnsignedInt(buffer, buffer.readerIndex());
errorCode = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
length -= 8;
if (length == 0) {
state = State.READ_FRAME_HEADER;
} else {
paddingLength = length;
state = State.SKIP_FRAME_PADDING;
}
delegate.readGoAwayFrame(lastStreamId, errorCode);
break;
case READ_WINDOW_UPDATE_FRAME:
// Wait until entire frame is readable
if (buffer.readableBytes() < length) {
return;
}
int windowSizeIncrement = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(length);
if (windowSizeIncrement == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid Window Size Increment");
} else {
state = State.READ_FRAME_HEADER;
delegate.readWindowUpdateFrame(streamId, windowSizeIncrement);
}
break;
case READ_CONTINUATION_FRAME_HEADER:
// Wait until entire frame header is readable
if (buffer.readableBytes() < HTTP_FRAME_HEADER_SIZE) {
return;
}
// Read and validate continuation frame header fields
int prevStreamId = streamId;
readFrameHeader(buffer);
// TODO(jpinner) Sec 4.2 FRAME_SIZE_ERROR
// TODO(jpinner) invalid flags
if (type != HTTP_CONTINUATION_FRAME || streamId != prevStreamId) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid Continuation Frame");
} else {
paddingLength = 0;
state = State.READ_HEADER_BLOCK;
}
break;
case READ_HEADER_BLOCK:
if (length == paddingLength) {
boolean endHeaders = hasFlag(flags, HTTP_FLAG_END_HEADERS);
if (endHeaders) {
state = State.SKIP_FRAME_PADDING;
delegate.readHeaderBlockEnd();
} else {
state = State.SKIP_FRAME_PADDING_CONTINUATION;
}
break;
}
if (!buffer.isReadable()) {
return;
}
int readableBytes = Math.min(buffer.readableBytes(), length - paddingLength);
ByteBuf headerBlockFragment = buffer.readBytes(readableBytes);
length -= readableBytes;
delegate.readHeaderBlock(headerBlockFragment);
break;
case SKIP_FRAME_PADDING:
int numBytes = Math.min(buffer.readableBytes(), length);
buffer.skipBytes(numBytes);
length -= numBytes;
if (length == 0) {
state = State.READ_FRAME_HEADER;
break;
}
return;
case SKIP_FRAME_PADDING_CONTINUATION:
int numPaddingBytes = Math.min(buffer.readableBytes(), length);
buffer.skipBytes(numPaddingBytes);
length -= numPaddingBytes;
if (length == 0) {
state = State.READ_CONTINUATION_FRAME_HEADER;
break;
}
return;
case FRAME_ERROR:
buffer.skipBytes(buffer.readableBytes());
return;
default:
throw new Error("Shouldn't reach here.");
}
}
}
/**
* Reads the HTTP/2 Frame Header and sets the length, type, flags, and streamId member variables.
*
* @param buffer input buffer containing the entire 9-octet header
*/
private void readFrameHeader(ByteBuf buffer) {
int frameOffset = buffer.readerIndex();
length = getUnsignedMedium(buffer, frameOffset);
type = buffer.getUnsignedByte(frameOffset + 3);
flags = buffer.getByte(frameOffset + 4);
streamId = getUnsignedInt(buffer, frameOffset + 5);
buffer.skipBytes(HTTP_FRAME_HEADER_SIZE);
}
private static boolean hasFlag(byte flags, byte flag) {
return (flags & flag) != 0;
}
private static boolean frameHasPadding(int type, byte flags) {
switch (type) {
case HTTP_DATA_FRAME:
case HTTP_HEADERS_FRAME:
case HTTP_PUSH_PROMISE_FRAME:
return hasFlag(flags, HTTP_FLAG_PADDED);
default:
return false;
}
}
private static State getNextState(int length, int type) {
switch (type) {
case HTTP_DATA_FRAME:
return State.READ_DATA_FRAME;
case HTTP_HEADERS_FRAME:
return State.READ_HEADERS_FRAME;
case HTTP_PRIORITY_FRAME:
return State.READ_PRIORITY_FRAME;
case HTTP_RST_STREAM_FRAME:
return State.READ_RST_STREAM_FRAME;
case HTTP_SETTINGS_FRAME:
return State.READ_SETTINGS_FRAME;
case HTTP_PUSH_PROMISE_FRAME:
return State.READ_PUSH_PROMISE_FRAME;
case HTTP_PING_FRAME:
return State.READ_PING_FRAME;
case HTTP_GOAWAY_FRAME:
return State.READ_GOAWAY_FRAME;
case HTTP_WINDOW_UPDATE_FRAME:
return State.READ_WINDOW_UPDATE_FRAME;
case HTTP_CONTINUATION_FRAME:
throw new Error("Shouldn't reach here.");
default:
if (length != 0) {
return State.SKIP_FRAME_PADDING;
} else {
return State.READ_FRAME_HEADER;
}
}
}
private static boolean isValidFrameHeader(int length, short type, byte flags, int streamId) {
if (length > HTTP_MAX_LENGTH) {
return false;
}
int minLength;
switch (type) {
case HTTP_DATA_FRAME:
if (hasFlag(flags, HTTP_FLAG_PADDED)) {
minLength = 1;
} else {
minLength = 0;
}
return length >= minLength && streamId != 0;
case HTTP_HEADERS_FRAME:
if (hasFlag(flags, HTTP_FLAG_PADDED)) {
minLength = 1;
} else {
minLength = 0;
}
if (hasFlag(flags, HTTP_FLAG_PRIORITY)) {
minLength += 5;
}
return length >= minLength && streamId != 0;
case HTTP_PRIORITY_FRAME:
return length == 5 && streamId != 0;
case HTTP_RST_STREAM_FRAME:
return length == 4 && streamId != 0;
case HTTP_SETTINGS_FRAME:
boolean lengthValid = hasFlag(flags, HTTP_FLAG_ACK) ? length == 0 : (length % 6) == 0;
return lengthValid && streamId == 0;
case HTTP_PUSH_PROMISE_FRAME:
if (hasFlag(flags, HTTP_FLAG_PADDED)) {
minLength = 5;
} else {
minLength = 4;
}
return length >= minLength && streamId != 0;
case HTTP_PING_FRAME:
return length == 8 && streamId == 0;
case HTTP_GOAWAY_FRAME:
return length >= 8 && streamId == 0;
case HTTP_WINDOW_UPDATE_FRAME:
return length == 4;
case HTTP_CONTINUATION_FRAME:
return false;
default:
return true;
}
}
private static boolean isValidPaddingLength(
int length, short type, byte flags, int paddingLength) {
switch (type) {
case HTTP_DATA_FRAME:
return length >= paddingLength;
case HTTP_HEADERS_FRAME:
if (hasFlag(flags, HTTP_FLAG_PRIORITY)) {
return length >= paddingLength + 5;
} else {
return length >= paddingLength;
}
case HTTP_PUSH_PROMISE_FRAME:
return length >= paddingLength + 4;
default:
throw new Error("Shouldn't reach here.");
}
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpFrameDecoderDelegate.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.buffer.ByteBuf;
/**
* Callback interface for {@link HttpFrameDecoder}.
*/
public interface HttpFrameDecoderDelegate {
/**
* Called when a DATA frame is received.
*/
void readDataFramePadding(int streamId, boolean endStream, int padding);
/**
* Called when a DATA frame is received.
*/
void readDataFrame(int streamId, boolean endStream, boolean endSegment, ByteBuf data);
/**
* Called when a HEADERS frame is received.
* The Header Block Fragment is not included. See readHeaderBlock().
*/
void readHeadersFrame(
int streamId,
boolean endStream,
boolean endSegment,
boolean exclusive,
int dependency,
int weight
);
/**
* Called when a PRIORITY frame is received.
*/
void readPriorityFrame(int streamId, boolean exclusive, int dependency, int weight);
/**
* Called when a RST_STREAM frame is received.
*/
void readRstStreamFrame(int streamId, int errorCode);
/**
* Called when a SETTINGS frame is received.
* Settings are not included. See readSetting().
*/
void readSettingsFrame(boolean ack);
/**
* Called when an individual setting within a SETTINGS frame is received.
*/
void readSetting(int id, int value);
/**
* Called when the entire SETTINGS frame has been received.
*/
void readSettingsEnd();
/**
* Called when a PUSH_PROMISE frame is received.
* The Header Block Fragment is not included. See readHeaderBlock().
*/
void readPushPromiseFrame(int streamId, int promisedStreamId);
/**
* Called when a PING frame is received.
*/
void readPingFrame(long data, boolean ack);
/**
* Called when a GOAWAY frame is received.
*/
void readGoAwayFrame(int lastStreamId, int errorCode);
/**
* Called when a WINDOW_UPDATE frame is received.
*/
void readWindowUpdateFrame(int streamId, int windowSizeIncrement);
/**
* Called when the header block fragment within a HEADERS,
* PUSH_PROMISE, or CONTINUATION frame is received.
*/
void readHeaderBlock(ByteBuf headerBlockFragment);
/**
* Called when an entire header block has been received.
*/
void readHeaderBlockEnd();
/**
* Called when an unrecoverable connection error has occurred.
*/
void readFrameError(String message);
}
================================================
FILE: src/main/java/com/twitter/http2/HttpFrameEncoder.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.util.Set;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import static com.twitter.http2.HttpCodecUtil.HTTP_CONTINUATION_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_DATA_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_DEPENDENCY;
import static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_ACK;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_HEADERS;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_STREAM;
import static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_PRIORITY;
import static com.twitter.http2.HttpCodecUtil.HTTP_FRAME_HEADER_SIZE;
import static com.twitter.http2.HttpCodecUtil.HTTP_GOAWAY_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_HEADERS_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_MAX_LENGTH;
import static com.twitter.http2.HttpCodecUtil.HTTP_PING_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_PRIORITY_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_PUSH_PROMISE_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_RST_STREAM_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_SETTINGS_FRAME;
import static com.twitter.http2.HttpCodecUtil.HTTP_WINDOW_UPDATE_FRAME;
/**
* Encodes an HTTP/2 Frame into a {@link ByteBuf}.
*/
public class HttpFrameEncoder {
/**
* Encode an HTTP/2 DATA Frame
*/
public ByteBuf encodeDataFrame(int streamId, boolean endStream, ByteBuf data) {
byte flags = endStream ? HTTP_FLAG_END_STREAM : 0;
ByteBuf header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);
writeFrameHeader(header, data.readableBytes(), HTTP_DATA_FRAME, flags, streamId);
return Unpooled.wrappedBuffer(header, data);
}
/**
* Encode an HTTP/2 HEADERS Frame
*/
public ByteBuf encodeHeadersFrame(
int streamId,
boolean endStream,
boolean exclusive,
int dependency,
int weight,
ByteBuf headerBlock
) {
byte flags = endStream ? HTTP_FLAG_END_STREAM : 0;
boolean hasPriority = exclusive
|| dependency != HTTP_DEFAULT_DEPENDENCY || weight != HTTP_DEFAULT_WEIGHT;
if (hasPriority) {
flags |= HTTP_FLAG_PRIORITY;
}
int maxLength = hasPriority ? HTTP_MAX_LENGTH - 5 : HTTP_MAX_LENGTH;
boolean needsContinuations = headerBlock.readableBytes() > maxLength;
if (!needsContinuations) {
flags |= HTTP_FLAG_END_HEADERS;
}
int length = needsContinuations ? maxLength : headerBlock.readableBytes();
if (hasPriority) {
length += 5;
}
int frameLength = hasPriority ? HTTP_FRAME_HEADER_SIZE + 5 : HTTP_FRAME_HEADER_SIZE;
ByteBuf header = Unpooled.buffer(frameLength);
writeFrameHeader(header, length, HTTP_HEADERS_FRAME, flags, streamId);
if (hasPriority) {
if (exclusive) {
header.writeInt(dependency | 0x80000000);
} else {
header.writeInt(dependency);
}
header.writeByte(weight - 1);
length -= 5;
}
ByteBuf frame = Unpooled.wrappedBuffer(header, headerBlock.readSlice(length));
if (needsContinuations) {
while (headerBlock.readableBytes() > HTTP_MAX_LENGTH) {
header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);
writeFrameHeader(header, HTTP_MAX_LENGTH, HTTP_CONTINUATION_FRAME, (byte) 0, streamId);
frame = Unpooled.wrappedBuffer(frame, header, headerBlock.readSlice(HTTP_MAX_LENGTH));
}
header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);
writeFrameHeader(
header,
headerBlock.readableBytes(),
HTTP_CONTINUATION_FRAME,
HTTP_FLAG_END_HEADERS,
streamId
);
frame = Unpooled.wrappedBuffer(frame, header, headerBlock);
}
return frame;
}
/**
* Encode an HTTP/2 PRIORITY Frame
*/
public ByteBuf encodePriorityFrame(int streamId, boolean exclusive, int dependency, int weight) {
int length = 5;
byte flags = 0;
ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);
writeFrameHeader(frame, length, HTTP_PRIORITY_FRAME, flags, streamId);
if (exclusive) {
frame.writeInt(dependency | 0x80000000);
} else {
frame.writeInt(dependency);
}
frame.writeByte(weight - 1);
return frame;
}
/**
* Encode an HTTP/2 RST_STREAM Frame
*/
public ByteBuf encodeRstStreamFrame(int streamId, int errorCode) {
int length = 4;
byte flags = 0;
ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);
writeFrameHeader(frame, length, HTTP_RST_STREAM_FRAME, flags, streamId);
frame.writeInt(errorCode);
return frame;
}
/**
* Encode an HTTP/2 SETTINGS Frame
*/
public ByteBuf encodeSettingsFrame(HttpSettingsFrame httpSettingsFrame) {
Set<Integer> ids = httpSettingsFrame.getIds();
int length = ids.size() * 6;
byte flags = httpSettingsFrame.isAck() ? HTTP_FLAG_ACK : 0;
int streamId = 0;
ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);
writeFrameHeader(frame, length, HTTP_SETTINGS_FRAME, flags, streamId);
for (int id : ids) {
frame.writeShort(id);
frame.writeInt(httpSettingsFrame.getValue(id));
}
return frame;
}
/**
* Encode an HTTP/2 PUSH_PROMISE Frame
*/
public ByteBuf encodePushPromiseFrame(int streamId, int promisedStreamId, ByteBuf headerBlock) {
boolean needsContinuations = headerBlock.readableBytes() > HTTP_MAX_LENGTH - 4;
int length = needsContinuations ? HTTP_MAX_LENGTH - 4 : headerBlock.readableBytes();
byte flags = needsContinuations ? 0 : HTTP_FLAG_END_HEADERS;
ByteBuf header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + 4);
writeFrameHeader(header, length + 4, HTTP_PUSH_PROMISE_FRAME, flags, streamId);
header.writeInt(promisedStreamId);
ByteBuf frame = Unpooled.wrappedBuffer(header, headerBlock.readSlice(length));
if (needsContinuations) {
while (headerBlock.readableBytes() > HTTP_MAX_LENGTH) {
header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);
writeFrameHeader(header, HTTP_MAX_LENGTH, HTTP_CONTINUATION_FRAME, (byte) 0, streamId);
frame = Unpooled.wrappedBuffer(frame, header, headerBlock.readSlice(HTTP_MAX_LENGTH));
}
header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);
writeFrameHeader(
header,
headerBlock.readableBytes(),
HTTP_CONTINUATION_FRAME,
HTTP_FLAG_END_HEADERS,
streamId
);
frame = Unpooled.wrappedBuffer(frame, header, headerBlock);
}
return frame;
}
/**
* Encode an HTTP/2 PING Frame
*/
public ByteBuf encodePingFrame(long data, boolean ack) {
int length = 8;
byte flags = ack ? HTTP_FLAG_ACK : 0;
int streamId = 0;
ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);
writeFrameHeader(frame, length, HTTP_PING_FRAME, flags, streamId);
frame.writeLong(data);
return frame;
}
/**
* Encode an HTTP/2 GOAWAY Frame
*/
public ByteBuf encodeGoAwayFrame(int lastStreamId, int errorCode) {
int length = 8;
byte flags = 0;
int streamId = 0;
ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);
writeFrameHeader(frame, length, HTTP_GOAWAY_FRAME, flags, streamId);
frame.writeInt(lastStreamId);
frame.writeInt(errorCode);
return frame;
}
/**
* Encode an HTTP/2 WINDOW_UPDATE Frame
*/
public ByteBuf encodeWindowUpdateFrame(int streamId, int windowSizeIncrement) {
int length = 4;
byte flags = 0;
ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);
writeFrameHeader(frame, length, HTTP_WINDOW_UPDATE_FRAME, flags, streamId);
frame.writeInt(windowSizeIncrement);
return frame;
}
private void writeFrameHeader(ByteBuf buffer, int length, int type, byte flags, int streamId) {
buffer.writeMedium(length);
buffer.writeByte(type);
buffer.writeByte(flags);
buffer.writeInt(streamId);
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpGoAwayFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
/**
* An HTTP/2 GOAWAY Frame
*/
public interface HttpGoAwayFrame extends HttpFrame {
/**
* Returns the Last-Stream-ID of this frame.
*/
int getLastStreamId();
/**
* Sets the Last-Stream-ID of this frame. The Last-Stream-ID cannot be negative.
*/
HttpGoAwayFrame setLastStreamId(int lastStreamId);
/**
* Returns the error code of this frame.
*/
HttpErrorCode getErrorCode();
/**
* Sets the error code of this frame.
*/
HttpGoAwayFrame setErrorCode(HttpErrorCode errorCode);
}
================================================
FILE: src/main/java/com/twitter/http2/HttpHeaderBlockDecoder.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpHeaders;
import com.twitter.hpack.Decoder;
import com.twitter.hpack.HeaderListener;
final class HttpHeaderBlockDecoder {
private static final HeaderListener NULL_HEADER_LISTENER = new NullHeaderListener();
private Decoder decoder;
private ByteBuf cumulation;
public HttpHeaderBlockDecoder(int maxHeaderSize, int maxHeaderTableSize) {
decoder = new Decoder(maxHeaderSize, maxHeaderTableSize);
}
/**
* Set the maximum header table size allowed by the decoder.
* This is the value of SETTINGS_HEADER_TABLE_SIZE sent to the peer.
*
* @param maxHeaderTableSize the maximum header table size allowed by the decoder
*/
public void setMaxHeaderTableSize(int maxHeaderTableSize) {
decoder.setMaxHeaderTableSize(maxHeaderTableSize);
}
public void decode(ByteBuf headerBlock, final HttpHeaderBlockFrame frame) throws IOException {
HeaderListener headerListener = NULL_HEADER_LISTENER;
if (frame != null) {
headerListener = new HeaderListenerImpl(frame.headers());
}
if (cumulation == null) {
decoder.decode(new ByteBufInputStream(headerBlock), headerListener);
if (headerBlock.isReadable()) {
cumulation = Unpooled.buffer(headerBlock.readableBytes());
cumulation.writeBytes(headerBlock);
}
} else {
cumulation.writeBytes(headerBlock);
decoder.decode(new ByteBufInputStream(cumulation), headerListener);
if (cumulation.isReadable()) {
cumulation.discardReadBytes();
} else {
cumulation.release();
cumulation = null;
}
}
}
public void endHeaderBlock(final HttpHeaderBlockFrame frame) {
if (cumulation != null) {
if (cumulation.isReadable() && frame != null) {
frame.setInvalid();
}
cumulation.release();
cumulation = null;
}
boolean truncated = decoder.endHeaderBlock();
if (truncated && frame != null) {
frame.setTruncated();
}
}
private static final class NullHeaderListener implements HeaderListener {
@Override
public void addHeader(byte[] name, byte[] value, boolean sensitive) {
// No Op
}
}
private static final class HeaderListenerImpl implements HeaderListener {
private final HttpHeaders headers;
HeaderListenerImpl(HttpHeaders headers) {
this.headers = headers;
}
@Override
public void addHeader(byte[] name, byte[] value, boolean sensitive) {
String nameStr = new String(name, StandardCharsets.UTF_8);
// check for empty value
if (value.length == 0) {
addHeader(nameStr, "");
return;
}
// Sec. 8.1.3.3. Header Field Ordering
int index = 0;
int offset = 0;
while (index < value.length) {
while (index < value.length && value[index] != (byte) 0) {
index++;
}
if (index - offset == 0) {
addHeader(nameStr, "");
} else {
String valueStr = new String(value, offset, index - offset, StandardCharsets.UTF_8);
addHeader(nameStr, valueStr);
}
index++;
offset = index;
}
}
private void addHeader(String name, String value) {
boolean crumb = "cookie".equalsIgnoreCase(name);
if (value.length() == 0) {
if (crumb || headers.contains(name)) {
return;
}
}
if (crumb) {
// Sec. 8.1.3.4. Cookie Header Field
String cookie = headers.get(name);
if (cookie == null) {
headers.set(name, value);
} else {
headers.set(name, cookie + "; " + value);
}
} else {
headers.add(name, value);
}
}
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpHeaderBlockEncoder.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import com.twitter.hpack.Encoder;
public class HttpHeaderBlockEncoder {
private static final byte[] COOKIE = {'c', 'o', 'o', 'k', 'i', 'e'};
private static final byte[] EMPTY = {};
private int encoderMaxHeaderTableSize;
private int decoderMaxHeaderTableSize;
private int maxHeaderTableSize;
private Encoder encoder;
/**
* Create a new instance.
*/
public HttpHeaderBlockEncoder(int maxHeaderTableSize) {
encoderMaxHeaderTableSize = maxHeaderTableSize;
decoderMaxHeaderTableSize = maxHeaderTableSize;
this.maxHeaderTableSize = maxHeaderTableSize;
encoder = new Encoder(maxHeaderTableSize);
}
/**
* Set the maximum header table size allowed by the encoder.
*
* @param encoderMaxHeaderTableSize the maximum header table size allowed by the encoder
*/
public void setEncoderMaxHeaderTableSize(int encoderMaxHeaderTableSize) {
this.encoderMaxHeaderTableSize = encoderMaxHeaderTableSize;
if (encoderMaxHeaderTableSize < maxHeaderTableSize) {
maxHeaderTableSize = encoderMaxHeaderTableSize;
}
}
/**
* Set the maximum header table size allowed by the peer's encoder.
* This is the value of SETTINGS_HEADER_TABLE_SIZE received from the peer.
*
* @param decoderMaxHeaderTableSize the maximum header table size allowed by the decoder
*/
public void setDecoderMaxHeaderTableSize(int decoderMaxHeaderTableSize) {
this.decoderMaxHeaderTableSize = decoderMaxHeaderTableSize;
if (decoderMaxHeaderTableSize < maxHeaderTableSize) {
maxHeaderTableSize = decoderMaxHeaderTableSize;
}
}
/**
* Encode the header block frame.
*/
public ByteBuf encode(ChannelHandlerContext ctx, HttpHeaderBlockFrame frame) throws IOException {
ByteBuf buf = Unpooled.buffer();
ByteBufOutputStream out = new ByteBufOutputStream(buf);
// The current allowable max header table size is the
// minimum of the encoder and decoder allowable sizes
int allowableHeaderTableSize = Math.min(encoderMaxHeaderTableSize, decoderMaxHeaderTableSize);
// maxHeaderTableSize will hold the smallest size seen the
// last call to encode. This might be smaller than the
// current allowable max header table size
if (maxHeaderTableSize < allowableHeaderTableSize) {
encoder.setMaxHeaderTableSize(out, maxHeaderTableSize);
}
// Check if the current allowable size is equal to the encoder's
// capacity and set the new size if necessary
if (allowableHeaderTableSize != encoder.getMaxHeaderTableSize()) {
encoder.setMaxHeaderTableSize(out, allowableHeaderTableSize);
}
// Store the current allowable size for the next call
maxHeaderTableSize = allowableHeaderTableSize;
// Now we can encode headers
for (String name : frame.headers().names()) {
if ("cookie".equalsIgnoreCase(name)) {
// Sec. 8.1.3.4. Cookie Header Field
for (String value : frame.headers().getAll(name)) {
for (String crumb : value.split(";")) {
byte[] valueBytes = crumb.trim().getBytes(StandardCharsets.UTF_8);
encoder.encodeHeader(out, COOKIE, valueBytes, true);
}
}
} else {
byte[] nameBytes = name.toLowerCase(Locale.ENGLISH).getBytes(StandardCharsets.UTF_8);
// Sec. 8.1.3.3. Header Field Ordering
List<String> values = frame.headers().getAll(name);
if (values.size() == 0) {
encoder.encodeHeader(out, nameBytes, EMPTY, false);
} else {
for (String value : values) {
byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);
encoder.encodeHeader(out, nameBytes, valueBytes, false);
}
}
}
}
return buf;
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpHeaderBlockFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.handler.codec.http.HttpHeaders;
public interface HttpHeaderBlockFrame extends HttpStreamFrame {
/**
* Returns {@code true} if this header block is invalid.
* A RST_STREAM frame with code PROTOCOL_ERROR should be sent.
*/
boolean isInvalid();
/**
* Marks this header block as invalid.
*/
HttpHeaderBlockFrame setInvalid();
/**
* Returns {@code true} if this header block has been truncated due to length restrictions.
*/
boolean isTruncated();
/**
* Mark this header block as truncated.
*/
HttpHeaderBlockFrame setTruncated();
/**
* Returns the {@link HttpHeaders} of this frame.
*/
HttpHeaders headers();
}
================================================
FILE: src/main/java/com/twitter/http2/HttpHeadersFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
/**
* An HTTP/2 HEADERS Frame
*/
public interface HttpHeadersFrame extends HttpHeaderBlockFrame {
/**
* Returns {@code true} if this frame is the last frame to be transmitted on the stream.
*/
boolean isLast();
/**
* Sets if this frame is the last frame to be transmitted on the stream.
*/
HttpHeadersFrame setLast(boolean last);
/**
* Returns {@code true} if the dependency of the stream is exclusive.
*/
boolean isExclusive();
/**
* Sets if the dependency of the stream is exclusive.
*/
HttpHeadersFrame setExclusive(boolean exclusive);
/**
* Returns the dependency of the stream.
*/
int getDependency();
/**
* Sets the dependency of the stream. The dependency cannot be negative.
*/
HttpHeadersFrame setDependency(int dependency);
/**
* Returns the weight of the dependency of the stream.
*/
int getWeight();
/**
* Sets the weight of the dependency of the stream.
* The weight must be positive and cannot exceed 256 bytes.
*/
HttpHeadersFrame setWeight(int weight);
@Override
HttpHeadersFrame setStreamId(int streamId);
@Override
HttpHeadersFrame setInvalid();
@Override
HttpHeadersFrame setTruncated();
}
================================================
FILE: src/main/java/com/twitter/http2/HttpMessageProxy.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpVersion;
/**
* An {@link HttpMessage} decorator.
*/
class HttpMessageProxy implements HttpMessage {
private final HttpMessage message;
protected HttpMessageProxy(HttpMessage message) {
this.message = message;
}
@Override
public HttpVersion getProtocolVersion() {
return message.getProtocolVersion();
}
@Override
public HttpMessage setProtocolVersion(HttpVersion version) {
message.setProtocolVersion(version);
return this;
}
@Override
public HttpHeaders headers() {
return message.headers();
}
@Override
@Deprecated
public DecoderResult getDecoderResult() {
return message.getDecoderResult();
}
@Override
public void setDecoderResult(DecoderResult result) {
message.setDecoderResult(result);
}
@Override
public HttpVersion protocolVersion() {
return message.protocolVersion();
}
@Override
public DecoderResult decoderResult() {
return message.decoderResult();
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpPingFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
/**
* An HTTP/2 PING Frame
*/
public interface HttpPingFrame extends HttpFrame {
/**
* Returns the data payload of this frame.
*/
long getData();
/**
* Sets the data payload of this frame.
*/
HttpPingFrame setData(long data);
/**
* Returns {@code true} if this frame is in response to a PING.
*/
boolean isPong();
/**
* Sets if this frame is in response to a PING.
*/
HttpPingFrame setPong(boolean pong);
}
================================================
FILE: src/main/java/com/twitter/http2/HttpPriorityFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
/**
* An HTTP/2 PRIORITY Frame
*/
public interface HttpPriorityFrame extends HttpFrame {
/**
* Returns the stream identifier of this frame.
*/
int getStreamId();
/**
* Sets the stream identifier of this frame. The stream identifier must be positive.
*/
HttpPriorityFrame setStreamId(int streamId);
/**
* Returns {@code true} if the dependency of the stream is exclusive.
*/
boolean isExclusive();
/**
* Sets if the dependency of the stream is exclusive.
*/
HttpPriorityFrame setExclusive(boolean exclusive);
/**
* Returns the dependency of the stream.
*/
int getDependency();
/**
* Sets the dependency of the stream. The dependency cannot be negative.
*/
HttpPriorityFrame setDependency(int dependency);
/**
* Returns the weight of the dependency of the stream.
*/
int getWeight();
/**
* Sets the weight of the dependency of the stream.
* The weight must be positive and cannot exceed 256 bytes.
*/
HttpPriorityFrame setWeight(int weight);
}
================================================
FILE: src/main/java/com/twitter/http2/HttpProtocolException.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
public class HttpProtocolException extends Exception {
/**
* Creates a new instance.
*/
public HttpProtocolException() {
super();
}
/**
* Creates a new instance.
*/
public HttpProtocolException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new instance.
*/
public HttpProtocolException(String message) {
super(message);
}
/**
* Creates a new instance.
*/
public HttpProtocolException(Throwable cause) {
super(cause);
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpPushPromiseFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
/**
* An HTTP/2 PUSH_PROMISE Frame
*/
public interface HttpPushPromiseFrame extends HttpHeaderBlockFrame {
/**
* Returns the Promised-Stream-ID of this frame.
*/
int getPromisedStreamId();
/**
* Sets the Promised-Stream-ID of this frame. The Promised-Stream-ID must be positive.
*/
HttpPushPromiseFrame setPromisedStreamId(int promisedStreamId);
@Override
HttpPushPromiseFrame setStreamId(int streamId);
@Override
HttpPushPromiseFrame setInvalid();
@Override
HttpPushPromiseFrame setTruncated();
}
================================================
FILE: src/main/java/com/twitter/http2/HttpRequestProxy.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
/**
* An {@link HttpRequest} decorator.
*/
class HttpRequestProxy extends HttpMessageProxy implements HttpRequest {
private final HttpRequest request;
public HttpRequestProxy(HttpRequest request) {
super(request);
this.request = request;
}
public HttpRequest httpRequest() {
return request;
}
@Override
@Deprecated
public HttpMethod getMethod() {
return request.getMethod();
}
@Override
public HttpRequest setMethod(HttpMethod method) {
request.setMethod(method);
return this;
}
@Override
@Deprecated
public String getUri() {
return request.getUri();
}
@Override
public HttpRequest setUri(String uri) {
request.setUri(uri);
return this;
}
@Override
public HttpRequest setProtocolVersion(HttpVersion version) {
request.setProtocolVersion(version);
return this;
}
@Override
public String toString() {
return request.toString();
}
@Override
public HttpMethod method() {
return request.method();
}
@Override
public String uri() {
return request.uri();
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpResponseProxy.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
/**
* An {@link HttpResponse} decorator.
*/
public class HttpResponseProxy extends HttpMessageProxy implements HttpResponse {
private final HttpResponse response;
public HttpResponseProxy(HttpResponse response) {
super(response);
this.response = response;
}
@Override
@Deprecated
public HttpResponseStatus getStatus() {
return response.getStatus();
}
@Override
public HttpResponse setStatus(HttpResponseStatus status) {
response.setStatus(status);
return this;
}
@Override
public HttpResponse setProtocolVersion(HttpVersion version) {
response.setProtocolVersion(version);
return this;
}
@Override
public String toString() {
return response.toString();
}
@Override
public HttpResponseStatus status() {
return response.status();
}
}
================================================
FILE: src/main/java/com/twitter/http2/HttpRstStreamFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
/**
* An HTTP/2 RST_STREAM Frame
*/
public interface HttpRstStreamFrame extends HttpFrame {
/**
* Returns the stream identifier of this frame.
*/
int getStreamId();
/**
* Sets the stream identifier of this frame. The stream identifier must be positive.
*/
HttpRstStreamFrame setStreamId(int streamId);
/**
* Returns the error code of this frame.
*/
HttpErrorCode getErrorCode();
/**
* Sets the error code of this frame.
*/
HttpRstStreamFrame setErrorCode(HttpErrorCode errorCode);
}
================================================
FILE: src/main/java/com/twitter/http2/HttpSettingsFrame.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.util.Set;
/**
* An HTTP/2 SETTINGS Frame
*/
public interface HttpSettingsFrame extends HttpFrame {
int SETTINGS_HEADER_TABLE_SIZE = 1;
int SETTINGS_ENABLE_PUSH = 2;
int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
int SETTINGS_INITIAL_WINDOW_SIZE = 4;
int SETTINGS_MAX_FRAME_SIZE = 5;
int SETTINGS_MAX_HEADER_LIST_SIZE = 6;
/**
* Returns a {@code Set} of the setting IDs.
* The set's iterator will return the IDs in ascending order.
*/
Set<Integer> getIds();
/**
* Returns {@code true} if the setting ID has a value.
*/
boolean isSet(int id);
/**
* Returns the value of the setting ID.
* Returns -1 if the setting ID is not set.
*/
int getValue(int id);
/**
* Sets the value of the setting ID.
* The ID cannot be negative and cannot exceed 65535.
*/
HttpSettingsFrame setValue(int id, int value);
/**
* Removes the value of the setting ID.
*/
HttpSettingsFrame removeValue(int id);
/**
* Returns {@code true} if this frame is an acknowledgement frame.
*/
boolean isAck();
/**
* Sets if this frame is acknowledgement frame.
*/
HttpSettingsFrame setAck(boolean ack);
}
================================================
FILE: src/main/java/com/twitter/http2/HttpStreamDecoder.java
================================================
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed 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 com.twitter.http2;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
/**
* Decodes {@link HttpFrame}s into {@link StreamedHttpRequest}s.
*/
public class HttpStreamDecoder extends MessageToMessageDecoder<Object> {
private static final CancellationException CANCELLATION_EXCEPTION =
new CancellationException("HTTP/2 RST_STREAM Frame Received");
private final Map<Integer, StreamedHttpMessage> messageMap =
new ConcurrentHashMap<Integer, StreamedHttpMessage>();
private final Map<Integer, LastHttpContent> trailerMap =
new ConcurrentHashMap<Integer, LastHttpContent>();
@Override
protected void decode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
if (msg instanceof HttpHeadersFrame) {
HttpHeadersFrame httpHeadersFrame = (HttpHeadersFrame) msg;
int streamId = httpHeadersFrame.getStreamId();
StreamedHttpMessage message = messageMap.get(streamId);
if (message == null) {
if (httpHeadersFrame.headers().contains(":status")) {
// If a client receives a reply with a truncated header block,
// reply with a RST_STREAM frame with error code INTERNAL_ERROR.
if (httpHeadersFrame.isTruncated()) {
HttpRstStreamFrame httpRstStreamFrame =
new DefaultHttpRstStreamFrame(streamId, HttpErrorCode.INTERNAL_ERROR);
out.add(httpRstStreamFrame);
return;
}
try {
StreamedHttpResponse response = createHttpResponse(httpHeadersFrame);
if (httpHeadersFrame.isLast()) {
HttpHeaders.setContentLength(response, 0);
response.getContent().close();
} else {
// Response body will follow in a series of Data Frames
if (!HttpHeaders.isContentLengthSet(response)) {
HttpHeaders.setTransferEncodingChunked(response);
}
messageMap.put(streamId, response);
}
out.add(response);
} catch (Exception e) {
// If a client receives a SYN_REPLY without valid getStatus and version headers
// the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR
HttpRstStreamFrame httpRstStreamFrame =
new DefaultHttpRstStreamFrame(streamId, HttpErrorCode.PROTOCOL_ERROR);
ctx.writeAndFlush(httpRstStreamFrame);
out.add(httpRstStreamFrame);
}
} else {
// If a client sends a request with a truncated header block, the server must
// reply with a HTTP 431 REQUEST HEADER FIELDS TOO LARGE reply.
if (httpHeadersFrame.isTruncated()) {
httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);
httpHeadersFrame.setLast(true);
httpHeadersFrame.headers().set(
":status", HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE.code());
ctx.writeAndFlush(httpHeadersFrame);
return;
}
try {
message = createHttpRequest(httpHeadersFrame);
if (httpHeadersFrame.isLast()) {
message.setDecoderResult(DecoderResult.SUCCESS);
message.getContent().close();
} else {
// Request body will follow in a series of Data Frames
messageMap.put(streamId, message);
}
out.add(message);
} catch (Exception e) {
// If a client sends a SYN_STREAM without all of the method, url (host and path),
// scheme, and version headers the server must reply with a HTTP 400 BAD REQUEST reply.
// Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);
httpHeadersFrame.setLast(true);
httpHeadersFrame.headers().set(":status", HttpResponseStatus.BAD_REQUEST.code());
ctx.writeAndFlush(httpHeadersFrame);
}
}
} else {
LastHttpContent trailer = trailerMap.remove(streamId);
if (trailer == null) {
trailer = new DefaultLastHttpContent();
}
// Ignore trailers in a truncated HEADERS frame.
if (!httpHeadersFrame.isTruncated()) {
for (Map.Entry<String, String> e: httpHeadersFrame.headers()) {
trailer.trailingHeaders().add(e.getKey(), e.getValue());
}
}
if (httpHeadersFrame.isLast()) {
messageMap.remove(streamId);
message.addContent(trailer);
} else {
trailerMap.put(streamId, trailer);
}
}
} else if (msg instanceof HttpDataFrame) {
HttpDataFrame httpDataFrame = (HttpDataFrame) msg;
int streamId = httpDataFrame.getStreamId();
StreamedHttpMessage message = messageMap.get(streamId);
// If message is not in map discard Data Frame.
if (message == null) {
return;
}
ByteBuf content = httpDataFrame.content();
if (content.isReadable()) {
content.retain();
message.addContent(new DefaultHttpContent(content));
}
if (httpDataFrame.isLast()) {
messageMap.remove(streamId);
message.addContent(LastHttpContent.EMPTY_LAST_CONTENT);
message.setDecoderResult(DecoderResult.SUCCESS);
}
} else if (msg instanceof HttpRstStreamFrame) {
HttpRstStreamFrame httpRstStreamFrame = (HttpRstStreamFrame) msg;
int streamId = httpRstStreamFrame.getStreamId();
StreamedHttpMessage message = messageMap.remove(streamId);
if (message != null) {
message.getContent().close();
message.setDecoderResult(DecoderResult.failure(CANCELLATION_EXCEPTION));
}
} else {
// HttpGoAwayFrame
out.add(msg);
}
}
private StreamedHttpRequest createHttpRequest(HttpHeadersFrame httpHeadersFrame)
throws Exception {
// Create the first line of the request from the name/value pairs
HttpMethod method = HttpMethod.valueOf(httpHeadersFrame.headers().get(":method"));
String url = httpHeadersFrame.headers().get(":path");
httpHeadersFrame.headers().remove(":method");
httpHeadersFrame.headers().remove(":path");
StreamedHttpRequest request = new StreamedHttpRequest(HttpVersion.HTTP_1_1, method, url);
// Remove the scheme header
httpHeadersFrame.headers().remove(":scheme");
// Replace the SPDY host header with the HTTP host header
String host = httpHeadersFrame.headers().get(":authority");
httpHeadersFrame.headers().remove(":authority");
httpHeadersFrame.headers().set("host", host);
for (Map.Entry<String, String> e : httpHeadersFrame.headers()) {
String name = e.getKey();
String value = e.getValue();
if (name.charAt(0) != ':') {
request.headers().add(name, value);
}
}
// Set the Stream-ID as a header
request.headers().set("X-SPDY-Stream-ID", httpHeadersFrame.getStreamId());
// The Connection and Keep-Alive headers are no longer valid
HttpHeaders.setKeepAlive(request, true);
// Transfer-Encoding header is not valid
request.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
if (httpHeadersFrame.isLast()) {
request.getContent().c
gitextract_lusmlr8n/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
└── src/
├── main/
│ └── java/
│ └── com/
│ └── twitter/
│ └── http2/
│ ├── DefaultHttp2Headers.java
│ ├── DefaultHttpDataFrame.java
│ ├── DefaultHttpGoAwayFrame.java
│ ├── DefaultHttpHeaderBlockFrame.java
│ ├── DefaultHttpHeadersFrame.java
│ ├── DefaultHttpPingFrame.java
│ ├── DefaultHttpPriorityFrame.java
│ ├── DefaultHttpPushPromiseFrame.java
│ ├── DefaultHttpRstStreamFrame.java
│ ├── DefaultHttpSettingsFrame.java
│ ├── DefaultHttpStreamFrame.java
│ ├── DefaultHttpWindowUpdateFrame.java
│ ├── HttpCodecUtil.java
│ ├── HttpConnection.java
│ ├── HttpConnectionHandler.java
│ ├── HttpDataFrame.java
│ ├── HttpErrorCode.java
│ ├── HttpFrame.java
│ ├── HttpFrameDecoder.java
│ ├── HttpFrameDecoderDelegate.java
│ ├── HttpFrameEncoder.java
│ ├── HttpGoAwayFrame.java
│ ├── HttpHeaderBlockDecoder.java
│ ├── HttpHeaderBlockEncoder.java
│ ├── HttpHeaderBlockFrame.java
│ ├── HttpHeadersFrame.java
│ ├── HttpMessageProxy.java
│ ├── HttpPingFrame.java
│ ├── HttpPriorityFrame.java
│ ├── HttpProtocolException.java
│ ├── HttpPushPromiseFrame.java
│ ├── HttpRequestProxy.java
│ ├── HttpResponseProxy.java
│ ├── HttpRstStreamFrame.java
│ ├── HttpSettingsFrame.java
│ ├── HttpStreamDecoder.java
│ ├── HttpStreamEncoder.java
│ ├── HttpStreamFrame.java
│ ├── HttpWindowUpdateFrame.java
│ ├── Pipe.java
│ ├── StreamedHttpMessage.java
│ ├── StreamedHttpRequest.java
│ └── StreamedHttpResponse.java
└── test/
└── java/
└── com/
└── twitter/
└── http2/
├── HttpFrameDecoderTest.java
├── HttpFrameEncoderTest.java
├── HttpHeaderCompressionTest.java
├── HttpRequestProxyTest.java
├── HttpResponseProxyTest.java
└── PipeTest.java
SYMBOL INDEX (589 symbols across 49 files)
FILE: src/main/java/com/twitter/http2/DefaultHttp2Headers.java
class DefaultHttp2Headers (line 11) | public class DefaultHttp2Headers extends DefaultHttpHeaders {
method process (line 13) | @Override
method validateChar (line 20) | private static void validateChar(char character) {
method validateName (line 44) | @Override
method DefaultHttp2Headers (line 62) | public DefaultHttp2Headers() {
FILE: src/main/java/com/twitter/http2/DefaultHttpDataFrame.java
class DefaultHttpDataFrame (line 27) | public class DefaultHttpDataFrame extends DefaultHttpStreamFrame impleme...
method DefaultHttpDataFrame (line 37) | public DefaultHttpDataFrame(int streamId) {
method DefaultHttpDataFrame (line 47) | public DefaultHttpDataFrame(int streamId, ByteBuf data) {
method validate (line 55) | private static ByteBuf validate(ByteBuf data) {
method isLast (line 63) | @Override
method setLast (line 68) | @Override
method setStreamId (line 74) | @Override
method content (line 80) | @Override
method copy (line 88) | @Override
method duplicate (line 95) | @Override
method refCnt (line 102) | @Override
method retain (line 107) | @Override
method retain (line 113) | @Override
method release (line 119) | @Override
method release (line 124) | @Override
method toString (line 129) | @Override
method touch (line 149) | @Override
method touch (line 155) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpGoAwayFrame.java
class DefaultHttpGoAwayFrame (line 23) | public class DefaultHttpGoAwayFrame implements HttpGoAwayFrame {
method DefaultHttpGoAwayFrame (line 34) | public DefaultHttpGoAwayFrame(int lastStreamId, int code) {
method DefaultHttpGoAwayFrame (line 44) | public DefaultHttpGoAwayFrame(int lastStreamId, HttpErrorCode errorCod...
method getLastStreamId (line 49) | @Override
method setLastStreamId (line 54) | @Override
method getErrorCode (line 64) | @Override
method setErrorCode (line 69) | @Override
method toString (line 75) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpHeaderBlockFrame.java
class DefaultHttpHeaderBlockFrame (line 26) | public abstract class DefaultHttpHeaderBlockFrame extends DefaultHttpStr...
method DefaultHttpHeaderBlockFrame (line 38) | protected DefaultHttpHeaderBlockFrame(int streamId) {
method isInvalid (line 42) | @Override
method setInvalid (line 47) | @Override
method isTruncated (line 53) | @Override
method setTruncated (line 58) | @Override
method headers (line 64) | @Override
method appendHeaders (line 69) | protected void appendHeaders(StringBuilder buf) {
FILE: src/main/java/com/twitter/http2/DefaultHttpHeadersFrame.java
class DefaultHttpHeadersFrame (line 26) | public class DefaultHttpHeadersFrame extends DefaultHttpHeaderBlockFrame
method DefaultHttpHeadersFrame (line 39) | public DefaultHttpHeadersFrame(int streamId) {
method isLast (line 43) | @Override
method setLast (line 48) | @Override
method isExclusive (line 54) | @Override
method setExclusive (line 59) | @Override
method getDependency (line 65) | @Override
method setDependency (line 70) | @Override
method getWeight (line 80) | @Override
method setWeight (line 85) | @Override
method setStreamId (line 95) | @Override
method setInvalid (line 101) | @Override
method setTruncated (line 107) | @Override
method toString (line 113) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpPingFrame.java
class DefaultHttpPingFrame (line 23) | public class DefaultHttpPingFrame implements HttpPingFrame {
method DefaultHttpPingFrame (line 33) | public DefaultHttpPingFrame(long data) {
method getData (line 37) | @Override
method setData (line 42) | @Override
method isPong (line 48) | @Override
method setPong (line 53) | @Override
method toString (line 59) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpPriorityFrame.java
class DefaultHttpPriorityFrame (line 23) | public class DefaultHttpPriorityFrame implements HttpPriorityFrame {
method DefaultHttpPriorityFrame (line 38) | public DefaultHttpPriorityFrame(int streamId, boolean exclusive, int d...
method getStreamId (line 45) | @Override
method setStreamId (line 50) | @Override
method isExclusive (line 60) | @Override
method setExclusive (line 65) | @Override
method getDependency (line 71) | @Override
method setDependency (line 76) | @Override
method getWeight (line 86) | @Override
method setWeight (line 91) | @Override
method toString (line 101) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpPushPromiseFrame.java
class DefaultHttpPushPromiseFrame (line 23) | public class DefaultHttpPushPromiseFrame extends DefaultHttpHeaderBlockF...
method DefaultHttpPushPromiseFrame (line 33) | public DefaultHttpPushPromiseFrame(int streamId, int promisedStreamId) {
method getPromisedStreamId (line 38) | @Override
method setPromisedStreamId (line 43) | @Override
method setStreamId (line 53) | @Override
method setInvalid (line 59) | @Override
method setTruncated (line 65) | @Override
method toString (line 71) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpRstStreamFrame.java
class DefaultHttpRstStreamFrame (line 23) | public class DefaultHttpRstStreamFrame implements HttpRstStreamFrame {
method DefaultHttpRstStreamFrame (line 34) | public DefaultHttpRstStreamFrame(int streamId, int code) {
method DefaultHttpRstStreamFrame (line 44) | public DefaultHttpRstStreamFrame(int streamId, HttpErrorCode errorCode) {
method getStreamId (line 49) | @Override
method setStreamId (line 54) | @Override
method getErrorCode (line 64) | @Override
method setErrorCode (line 69) | @Override
method toString (line 75) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpSettingsFrame.java
class DefaultHttpSettingsFrame (line 27) | public class DefaultHttpSettingsFrame implements HttpSettingsFrame {
method getIds (line 32) | @Override
method isSet (line 37) | @Override
method getValue (line 42) | @Override
method setValue (line 51) | @Override
method removeValue (line 60) | @Override
method isAck (line 66) | @Override
method setAck (line 71) | @Override
method getSettings (line 77) | private Set<Map.Entry<Integer, Integer>> getSettings() {
method appendSettings (line 81) | private void appendSettings(StringBuilder buf) {
method toString (line 91) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpStreamFrame.java
class DefaultHttpStreamFrame (line 21) | public abstract class DefaultHttpStreamFrame implements HttpStreamFrame {
method DefaultHttpStreamFrame (line 30) | protected DefaultHttpStreamFrame(int streamId) {
method getStreamId (line 34) | @Override
method setStreamId (line 39) | @Override
FILE: src/main/java/com/twitter/http2/DefaultHttpWindowUpdateFrame.java
class DefaultHttpWindowUpdateFrame (line 23) | public class DefaultHttpWindowUpdateFrame implements HttpWindowUpdateFra...
method DefaultHttpWindowUpdateFrame (line 34) | public DefaultHttpWindowUpdateFrame(int streamId, int windowSizeIncrem...
method getStreamId (line 39) | @Override
method setStreamId (line 44) | @Override
method getWindowSizeIncrement (line 54) | @Override
method setWindowSizeIncrement (line 59) | @Override
method toString (line 69) | @Override
FILE: src/main/java/com/twitter/http2/HttpCodecUtil.java
class HttpCodecUtil (line 20) | final class HttpCodecUtil {
method getUnsignedShort (line 53) | static int getUnsignedShort(ByteBuf buf, int offset) {
method getUnsignedMedium (line 61) | static int getUnsignedMedium(ByteBuf buf, int offset) {
method getUnsignedInt (line 70) | static int getUnsignedInt(ByteBuf buf, int offset) {
method getSignedInt (line 80) | static int getSignedInt(ByteBuf buf, int offset) {
method getSignedLong (line 90) | static long getSignedLong(ByteBuf buf, int offset) {
method isServerId (line 104) | static boolean isServerId(int streamId) {
method HttpCodecUtil (line 109) | private HttpCodecUtil() {
FILE: src/main/java/com/twitter/http2/HttpConnection.java
class HttpConnection (line 32) | final class HttpConnection {
method HttpConnection (line 48) | public HttpConnection(int sendWindowSize, int receiveWindowSize) {
method numActiveStreams (line 54) | int numActiveStreams(boolean remote) {
method noActiveStreams (line 62) | boolean noActiveStreams() {
method acceptStream (line 66) | void acceptStream(
method removeActiveStream (line 86) | private StreamState removeActiveStream(int streamId, boolean remote) {
method removeStream (line 101) | void removeStream(int streamId, boolean remote) {
method isRemoteSideClosed (line 108) | boolean isRemoteSideClosed(int streamId) {
method closeRemoteSide (line 114) | void closeRemoteSide(int streamId, boolean remote) {
method isLocalSideClosed (line 125) | boolean isLocalSideClosed(int streamId) {
method closeLocalSide (line 131) | void closeLocalSide(int streamId, boolean remote) {
method getSendWindowSize (line 142) | int getSendWindowSize(int streamId) {
method updateSendWindowSize (line 152) | int updateSendWindowSize(int streamId, int deltaWindowSize) {
method updateReceiveWindowSize (line 162) | int updateReceiveWindowSize(int streamId, int deltaWindowSize) {
method getReceiveWindowSizeLowerBound (line 178) | int getReceiveWindowSizeLowerBound(int streamId) {
method updateAllSendWindowSizes (line 188) | void updateAllSendWindowSizes(int deltaWindowSize) {
method updateAllReceiveWindowSizes (line 197) | void updateAllReceiveWindowSizes(int deltaWindowSize) {
method putPendingWrite (line 209) | boolean putPendingWrite(int streamId, PendingWrite evt) {
method getPendingWrite (line 215) | PendingWrite getPendingWrite(int streamId) {
method getPendingWrite (line 226) | PendingWrite getPendingWrite(Node node) {
method removePendingWrite (line 242) | PendingWrite removePendingWrite(int streamId) {
method setPriority (line 251) | boolean setPriority(int streamId, boolean exclusive, int dependency, i...
class StreamState (line 285) | private static final class StreamState {
method StreamState (line 295) | StreamState(
method isRemoteSideClosed (line 304) | boolean isRemoteSideClosed() {
method closeRemoteSide (line 308) | void closeRemoteSide() {
method isLocalSideClosed (line 312) | boolean isLocalSideClosed() {
method closeLocalSide (line 316) | void closeLocalSide() {
method getSendWindowSize (line 320) | int getSendWindowSize() {
method updateSendWindowSize (line 324) | int updateSendWindowSize(int deltaWindowSize) {
method updateReceiveWindowSize (line 328) | int updateReceiveWindowSize(int deltaWindowSize) {
method getReceiveWindowSizeLowerBound (line 332) | int getReceiveWindowSizeLowerBound() {
method setReceiveWindowSizeLowerBound (line 336) | void setReceiveWindowSizeLowerBound(int receiveWindowSizeLowerBound) {
method putPendingWrite (line 340) | boolean putPendingWrite(PendingWrite msg) {
method getPendingWrite (line 344) | PendingWrite getPendingWrite() {
method removePendingWrite (line 348) | PendingWrite removePendingWrite() {
method clearPendingWrites (line 352) | void clearPendingWrites(Throwable cause) {
class Node (line 363) | private static final class Node {
method Node (line 376) | public Node(StreamState state) {
method close (line 380) | public void close() {
method setWeight (line 384) | public void setWeight(int weight) {
method addDependent (line 391) | public void addDependent(boolean exclusive, Node node) {
method removeDependent (line 404) | public void removeDependent(Node node) {
class WeightComparator (line 411) | private static final class WeightComparator implements Comparator<Node> {
method compare (line 412) | @Override
class PendingWrite (line 418) | public static final class PendingWrite {
method PendingWrite (line 422) | PendingWrite(HttpDataFrame httpDataFrame, ChannelPromise promise) {
method fail (line 427) | void fail(Throwable cause) {
FILE: src/main/java/com/twitter/http2/HttpConnectionHandler.java
class HttpConnectionHandler (line 38) | public class HttpConnectionHandler extends ByteToMessageDecoder
method HttpConnectionHandler (line 104) | public HttpConnectionHandler(boolean server) {
method HttpConnectionHandler (line 111) | public HttpConnectionHandler(boolean server, boolean handleStreamWindo...
method HttpConnectionHandler (line 118) | public HttpConnectionHandler(boolean server, int maxChunkSize, int max...
method HttpConnectionHandler (line 125) | public HttpConnectionHandler(
method setConnectionReceiveWindowSize (line 135) | public void setConnectionReceiveWindowSize(int connectionReceiveWindow...
method handlerAdded (line 148) | @Override
method decode (line 154) | @Override
method readDataFramePadding (line 162) | @Override
method readDataFrame (line 219) | @Override
method readHeadersFrame (line 309) | @Override
method readPriorityFrame (line 368) | @Override
method readRstStreamFrame (line 380) | @Override
method readSettingsFrame (line 393) | @Override
method readSetting (line 407) | @Override
method readSettingsEnd (line 455) | @Override
method readPushPromiseFrame (line 481) | @Override
method readPingFrame (line 493) | @Override
method readGoAwayFrame (line 514) | @Override
method readWindowUpdateFrame (line 524) | @Override
method readHeaderBlock (line 555) | @Override
method readHeaderBlockEnd (line 568) | @Override
method readFrameError (line 590) | @Override
method bind (line 595) | @Override
method connect (line 602) | @Override
method disconnect (line 612) | @Override
method close (line 627) | @Override
method deregister (line 642) | @Override
method read (line 647) | @Override
method write (line 652) | @Override
method flush (line 895) | @Override
method issueConnectionError (line 907) | private void issueConnectionError(HttpErrorCode status) {
method issueStreamError (line 921) | private void issueStreamError(int streamId, HttpErrorCode errorCode) {
method isRemoteInitiatedId (line 937) | private boolean isRemoteInitiatedId(int id) {
method updateInitialSendWindowSize (line 943) | private synchronized void updateInitialSendWindowSize(int newInitialWi...
method updateInitialReceiveWindowSize (line 950) | private synchronized void updateInitialReceiveWindowSize(int newInitia...
method acceptStream (line 957) | private synchronized boolean acceptStream(
method setPriority (line 978) | private synchronized boolean setPriority(
method halfCloseStream (line 983) | private void halfCloseStream(int streamId, boolean remote, ChannelFutu...
method removeStream (line 994) | private void removeStream(int streamId, ChannelFuture future) {
method updateSendWindowSize (line 1001) | private void updateSendWindowSize(
method sendGoAwayFrame (line 1064) | private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelPromise...
method sendGoAwayFrame (line 1073) | private synchronized ChannelFuture sendGoAwayFrame(HttpErrorCode httpE...
class ConnectionErrorFutureListener (line 1084) | private final class ConnectionErrorFutureListener implements ChannelFu...
method operationComplete (line 1085) | @Override
class ClosingChannelFutureListener (line 1093) | private static final class ClosingChannelFutureListener implements Cha...
method ClosingChannelFutureListener (line 1097) | ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelPromi...
method operationComplete (line 1102) | public void operationComplete(ChannelFuture sentGoAwayFuture) throws...
FILE: src/main/java/com/twitter/http2/HttpDataFrame.java
type HttpDataFrame (line 24) | public interface HttpDataFrame extends ByteBufHolder, HttpStreamFrame {
method isLast (line 29) | boolean isLast();
method setLast (line 34) | HttpDataFrame setLast(boolean last);
method setStreamId (line 36) | @Override
method content (line 45) | @Override
method copy (line 48) | @Override
method duplicate (line 51) | @Override
method retain (line 54) | @Override
method retain (line 57) | @Override
FILE: src/main/java/com/twitter/http2/HttpErrorCode.java
class HttpErrorCode (line 18) | public class HttpErrorCode implements Comparable<HttpErrorCode> {
method valueOf (line 103) | public static HttpErrorCode valueOf(int code) {
method HttpErrorCode (line 146) | public HttpErrorCode(int code, String statusPhrase) {
method getCode (line 158) | public int getCode() {
method getStatusPhrase (line 165) | public String getStatusPhrase() {
method hashCode (line 169) | @Override
method equals (line 174) | @Override
method toString (line 183) | @Override
method compareTo (line 188) | @Override
FILE: src/main/java/com/twitter/http2/HttpFrame.java
type HttpFrame (line 21) | public interface HttpFrame {
FILE: src/main/java/com/twitter/http2/HttpFrameDecoder.java
class HttpFrameDecoder (line 49) | public class HttpFrameDecoder {
type State (line 69) | private enum State {
method HttpFrameDecoder (line 95) | public HttpFrameDecoder(boolean server, HttpFrameDecoderDelegate deleg...
method HttpFrameDecoder (line 102) | public HttpFrameDecoder(boolean server, HttpFrameDecoderDelegate deleg...
method decode (line 122) | public void decode(ByteBuf buffer) {
method readFrameHeader (line 461) | private void readFrameHeader(ByteBuf buffer) {
method hasFlag (line 470) | private static boolean hasFlag(byte flags, byte flag) {
method frameHasPadding (line 474) | private static boolean frameHasPadding(int type, byte flags) {
method getNextState (line 485) | private static State getNextState(int length, int type) {
method isValidFrameHeader (line 526) | private static boolean isValidFrameHeader(int length, short type, byte...
method isValidPaddingLength (line 586) | private static boolean isValidPaddingLength(
FILE: src/main/java/com/twitter/http2/HttpFrameDecoderDelegate.java
type HttpFrameDecoderDelegate (line 23) | public interface HttpFrameDecoderDelegate {
method readDataFramePadding (line 28) | void readDataFramePadding(int streamId, boolean endStream, int padding);
method readDataFrame (line 33) | void readDataFrame(int streamId, boolean endStream, boolean endSegment...
method readHeadersFrame (line 39) | void readHeadersFrame(
method readPriorityFrame (line 51) | void readPriorityFrame(int streamId, boolean exclusive, int dependency...
method readRstStreamFrame (line 56) | void readRstStreamFrame(int streamId, int errorCode);
method readSettingsFrame (line 62) | void readSettingsFrame(boolean ack);
method readSetting (line 67) | void readSetting(int id, int value);
method readSettingsEnd (line 72) | void readSettingsEnd();
method readPushPromiseFrame (line 78) | void readPushPromiseFrame(int streamId, int promisedStreamId);
method readPingFrame (line 83) | void readPingFrame(long data, boolean ack);
method readGoAwayFrame (line 88) | void readGoAwayFrame(int lastStreamId, int errorCode);
method readWindowUpdateFrame (line 93) | void readWindowUpdateFrame(int streamId, int windowSizeIncrement);
method readHeaderBlock (line 99) | void readHeaderBlock(ByteBuf headerBlockFragment);
method readHeaderBlockEnd (line 104) | void readHeaderBlockEnd();
method readFrameError (line 109) | void readFrameError(String message);
FILE: src/main/java/com/twitter/http2/HttpFrameEncoder.java
class HttpFrameEncoder (line 45) | public class HttpFrameEncoder {
method encodeDataFrame (line 50) | public ByteBuf encodeDataFrame(int streamId, boolean endStream, ByteBu...
method encodeHeadersFrame (line 60) | public ByteBuf encodeHeadersFrame(
method encodePriorityFrame (line 118) | public ByteBuf encodePriorityFrame(int streamId, boolean exclusive, in...
method encodeRstStreamFrame (line 135) | public ByteBuf encodeRstStreamFrame(int streamId, int errorCode) {
method encodeSettingsFrame (line 147) | public ByteBuf encodeSettingsFrame(HttpSettingsFrame httpSettingsFrame) {
method encodePushPromiseFrame (line 164) | public ByteBuf encodePushPromiseFrame(int streamId, int promisedStream...
method encodePingFrame (line 194) | public ByteBuf encodePingFrame(long data, boolean ack) {
method encodeGoAwayFrame (line 207) | public ByteBuf encodeGoAwayFrame(int lastStreamId, int errorCode) {
method encodeWindowUpdateFrame (line 221) | public ByteBuf encodeWindowUpdateFrame(int streamId, int windowSizeInc...
method writeFrameHeader (line 230) | private void writeFrameHeader(ByteBuf buffer, int length, int type, by...
FILE: src/main/java/com/twitter/http2/HttpGoAwayFrame.java
type HttpGoAwayFrame (line 21) | public interface HttpGoAwayFrame extends HttpFrame {
method getLastStreamId (line 26) | int getLastStreamId();
method setLastStreamId (line 31) | HttpGoAwayFrame setLastStreamId(int lastStreamId);
method getErrorCode (line 36) | HttpErrorCode getErrorCode();
method setErrorCode (line 41) | HttpGoAwayFrame setErrorCode(HttpErrorCode errorCode);
FILE: src/main/java/com/twitter/http2/HttpHeaderBlockDecoder.java
class HttpHeaderBlockDecoder (line 29) | final class HttpHeaderBlockDecoder {
method HttpHeaderBlockDecoder (line 36) | public HttpHeaderBlockDecoder(int maxHeaderSize, int maxHeaderTableSiz...
method setMaxHeaderTableSize (line 46) | public void setMaxHeaderTableSize(int maxHeaderTableSize) {
method decode (line 50) | public void decode(ByteBuf headerBlock, final HttpHeaderBlockFrame fra...
method endHeaderBlock (line 74) | public void endHeaderBlock(final HttpHeaderBlockFrame frame) {
class NullHeaderListener (line 90) | private static final class NullHeaderListener implements HeaderListener {
method addHeader (line 91) | @Override
class HeaderListenerImpl (line 97) | private static final class HeaderListenerImpl implements HeaderListener {
method HeaderListenerImpl (line 101) | HeaderListenerImpl(HttpHeaders headers) {
method addHeader (line 105) | @Override
method addHeader (line 133) | private void addHeader(String name, String value) {
FILE: src/main/java/com/twitter/http2/HttpHeaderBlockEncoder.java
class HttpHeaderBlockEncoder (line 30) | public class HttpHeaderBlockEncoder {
method HttpHeaderBlockEncoder (line 43) | public HttpHeaderBlockEncoder(int maxHeaderTableSize) {
method setEncoderMaxHeaderTableSize (line 55) | public void setEncoderMaxHeaderTableSize(int encoderMaxHeaderTableSize) {
method setDecoderMaxHeaderTableSize (line 68) | public void setDecoderMaxHeaderTableSize(int decoderMaxHeaderTableSize) {
method encode (line 78) | public ByteBuf encode(ChannelHandlerContext ctx, HttpHeaderBlockFrame ...
FILE: src/main/java/com/twitter/http2/HttpHeaderBlockFrame.java
type HttpHeaderBlockFrame (line 20) | public interface HttpHeaderBlockFrame extends HttpStreamFrame {
method isInvalid (line 26) | boolean isInvalid();
method setInvalid (line 31) | HttpHeaderBlockFrame setInvalid();
method isTruncated (line 36) | boolean isTruncated();
method setTruncated (line 41) | HttpHeaderBlockFrame setTruncated();
method headers (line 46) | HttpHeaders headers();
FILE: src/main/java/com/twitter/http2/HttpHeadersFrame.java
type HttpHeadersFrame (line 21) | public interface HttpHeadersFrame extends HttpHeaderBlockFrame {
method isLast (line 26) | boolean isLast();
method setLast (line 31) | HttpHeadersFrame setLast(boolean last);
method isExclusive (line 36) | boolean isExclusive();
method setExclusive (line 41) | HttpHeadersFrame setExclusive(boolean exclusive);
method getDependency (line 46) | int getDependency();
method setDependency (line 51) | HttpHeadersFrame setDependency(int dependency);
method getWeight (line 56) | int getWeight();
method setWeight (line 62) | HttpHeadersFrame setWeight(int weight);
method setStreamId (line 64) | @Override
method setInvalid (line 67) | @Override
method setTruncated (line 70) | @Override
FILE: src/main/java/com/twitter/http2/HttpMessageProxy.java
class HttpMessageProxy (line 26) | class HttpMessageProxy implements HttpMessage {
method HttpMessageProxy (line 30) | protected HttpMessageProxy(HttpMessage message) {
method getProtocolVersion (line 34) | @Override
method setProtocolVersion (line 39) | @Override
method headers (line 45) | @Override
method getDecoderResult (line 50) | @Override
method setDecoderResult (line 56) | @Override
method protocolVersion (line 61) | @Override
method decoderResult (line 66) | @Override
FILE: src/main/java/com/twitter/http2/HttpPingFrame.java
type HttpPingFrame (line 21) | public interface HttpPingFrame extends HttpFrame {
method getData (line 26) | long getData();
method setData (line 31) | HttpPingFrame setData(long data);
method isPong (line 36) | boolean isPong();
method setPong (line 41) | HttpPingFrame setPong(boolean pong);
FILE: src/main/java/com/twitter/http2/HttpPriorityFrame.java
type HttpPriorityFrame (line 21) | public interface HttpPriorityFrame extends HttpFrame {
method getStreamId (line 26) | int getStreamId();
method setStreamId (line 31) | HttpPriorityFrame setStreamId(int streamId);
method isExclusive (line 36) | boolean isExclusive();
method setExclusive (line 41) | HttpPriorityFrame setExclusive(boolean exclusive);
method getDependency (line 46) | int getDependency();
method setDependency (line 51) | HttpPriorityFrame setDependency(int dependency);
method getWeight (line 56) | int getWeight();
method setWeight (line 62) | HttpPriorityFrame setWeight(int weight);
FILE: src/main/java/com/twitter/http2/HttpProtocolException.java
class HttpProtocolException (line 18) | public class HttpProtocolException extends Exception {
method HttpProtocolException (line 23) | public HttpProtocolException() {
method HttpProtocolException (line 30) | public HttpProtocolException(String message, Throwable cause) {
method HttpProtocolException (line 37) | public HttpProtocolException(String message) {
method HttpProtocolException (line 44) | public HttpProtocolException(Throwable cause) {
FILE: src/main/java/com/twitter/http2/HttpPushPromiseFrame.java
type HttpPushPromiseFrame (line 21) | public interface HttpPushPromiseFrame extends HttpHeaderBlockFrame {
method getPromisedStreamId (line 26) | int getPromisedStreamId();
method setPromisedStreamId (line 31) | HttpPushPromiseFrame setPromisedStreamId(int promisedStreamId);
method setStreamId (line 33) | @Override
method setInvalid (line 36) | @Override
method setTruncated (line 39) | @Override
FILE: src/main/java/com/twitter/http2/HttpRequestProxy.java
class HttpRequestProxy (line 25) | class HttpRequestProxy extends HttpMessageProxy implements HttpRequest {
method HttpRequestProxy (line 29) | public HttpRequestProxy(HttpRequest request) {
method httpRequest (line 34) | public HttpRequest httpRequest() {
method getMethod (line 38) | @Override
method setMethod (line 44) | @Override
method getUri (line 50) | @Override
method setUri (line 56) | @Override
method setProtocolVersion (line 62) | @Override
method toString (line 68) | @Override
method method (line 73) | @Override
method uri (line 78) | @Override
FILE: src/main/java/com/twitter/http2/HttpResponseProxy.java
class HttpResponseProxy (line 25) | public class HttpResponseProxy extends HttpMessageProxy implements HttpR...
method HttpResponseProxy (line 29) | public HttpResponseProxy(HttpResponse response) {
method getStatus (line 34) | @Override
method setStatus (line 40) | @Override
method setProtocolVersion (line 46) | @Override
method toString (line 52) | @Override
method status (line 57) | @Override
FILE: src/main/java/com/twitter/http2/HttpRstStreamFrame.java
type HttpRstStreamFrame (line 21) | public interface HttpRstStreamFrame extends HttpFrame {
method getStreamId (line 26) | int getStreamId();
method setStreamId (line 31) | HttpRstStreamFrame setStreamId(int streamId);
method getErrorCode (line 36) | HttpErrorCode getErrorCode();
method setErrorCode (line 41) | HttpRstStreamFrame setErrorCode(HttpErrorCode errorCode);
FILE: src/main/java/com/twitter/http2/HttpSettingsFrame.java
type HttpSettingsFrame (line 23) | public interface HttpSettingsFrame extends HttpFrame {
method getIds (line 36) | Set<Integer> getIds();
method isSet (line 41) | boolean isSet(int id);
method getValue (line 47) | int getValue(int id);
method setValue (line 53) | HttpSettingsFrame setValue(int id, int value);
method removeValue (line 58) | HttpSettingsFrame removeValue(int id);
method isAck (line 63) | boolean isAck();
method setAck (line 68) | HttpSettingsFrame setAck(boolean ack);
FILE: src/main/java/com/twitter/http2/HttpStreamDecoder.java
class HttpStreamDecoder (line 38) | public class HttpStreamDecoder extends MessageToMessageDecoder<Object> {
method decode (line 48) | @Override
method createHttpRequest (line 188) | private StreamedHttpRequest createHttpRequest(HttpHeadersFrame httpHea...
method createHttpResponse (line 234) | private StreamedHttpResponse createHttpResponse(HttpHeadersFrame httpH...
FILE: src/main/java/com/twitter/http2/HttpStreamEncoder.java
class HttpStreamEncoder (line 39) | public class HttpStreamEncoder extends ChannelOutboundHandlerAdapter {
method write (line 45) | @Override
method getMessageFuture (line 89) | private ChannelPromise getMessageFuture(
class ChunkListener (line 129) | private class ChunkListener implements FutureListener<HttpContent> {
method ChunkListener (line 135) | ChunkListener(
method operationComplete (line 147) | @Override
method writeChunk (line 200) | protected void writeChunk(
method getFrameFuture (line 245) | private static ChannelPromise getFrameFuture(
class HttpFrameWriter (line 256) | private static class HttpFrameWriter implements ChannelFutureListener {
method HttpFrameWriter (line 262) | HttpFrameWriter(ChannelHandlerContext ctx, ChannelPromise promise, O...
method operationComplete (line 268) | public void operationComplete(ChannelFuture future) throws Exception {
method createHttpHeadersFrame (line 281) | private HttpHeadersFrame createHttpHeadersFrame(HttpRequest httpRequest)
method createHttpHeadersFrame (line 314) | private HttpHeadersFrame createHttpHeadersFrame(HttpResponse httpRespo...
method createHttpDataFrames (line 340) | private HttpDataFrame[] createHttpDataFrames(int streamId, ByteBuf con...
FILE: src/main/java/com/twitter/http2/HttpStreamFrame.java
type HttpStreamFrame (line 21) | public interface HttpStreamFrame extends HttpFrame {
method getStreamId (line 26) | int getStreamId();
method setStreamId (line 31) | HttpStreamFrame setStreamId(int streamId);
FILE: src/main/java/com/twitter/http2/HttpWindowUpdateFrame.java
type HttpWindowUpdateFrame (line 21) | public interface HttpWindowUpdateFrame extends HttpFrame {
method getStreamId (line 26) | int getStreamId();
method setStreamId (line 31) | HttpWindowUpdateFrame setStreamId(int streamId);
method getWindowSizeIncrement (line 36) | int getWindowSizeIncrement();
method setWindowSizeIncrement (line 42) | HttpWindowUpdateFrame setWindowSizeIncrement(int deltaWindowSize);
FILE: src/main/java/com/twitter/http2/Pipe.java
class Pipe (line 34) | public class Pipe<T> {
class Node (line 51) | private final class Node {
method Node (line 55) | Node(T message, Promise<Void> promise) {
method Pipe (line 67) | public Pipe() {
method send (line 84) | public Future<Void> send(T message) {
method receive (line 111) | public Future<T> receive() {
method close (line 135) | public void close() {
method isClosed (line 153) | public synchronized boolean isClosed() {
FILE: src/main/java/com/twitter/http2/StreamedHttpMessage.java
type StreamedHttpMessage (line 27) | public interface StreamedHttpMessage extends HttpMessage {
method getContent (line 37) | Pipe<HttpContent> getContent();
method addContent (line 50) | Future<Void> addContent(HttpContent content);
FILE: src/main/java/com/twitter/http2/StreamedHttpRequest.java
class StreamedHttpRequest (line 29) | public class StreamedHttpRequest extends HttpRequestProxy implements Str...
method StreamedHttpRequest (line 33) | public StreamedHttpRequest(HttpVersion version, HttpMethod method, Str...
method StreamedHttpRequest (line 37) | public StreamedHttpRequest(HttpRequest request) {
method getContent (line 41) | @Override
method addContent (line 46) | @Override
FILE: src/main/java/com/twitter/http2/StreamedHttpResponse.java
class StreamedHttpResponse (line 29) | public class StreamedHttpResponse extends HttpResponseProxy implements S...
method StreamedHttpResponse (line 33) | public StreamedHttpResponse(HttpVersion version, HttpResponseStatus st...
method StreamedHttpResponse (line 37) | public StreamedHttpResponse(HttpResponse response) {
method getContent (line 41) | @Override
method addContent (line 46) | @Override
FILE: src/test/java/com/twitter/http2/HttpFrameDecoderTest.java
class HttpFrameDecoderTest (line 38) | public class HttpFrameDecoderTest {
method createHandler (line 46) | @Before
method testClientConnectionPreface (line 52) | @Test
method testInvalidClientConnectionPreface (line 71) | @Test
method testHttpDataFrame (line 86) | @Test
method testEmptyHttpDataFrame (line 106) | @Test
method testLastHttpDataFrame (line 120) | @Test
method testLastSegmentHttpDataFrame (line 134) | @Test
method testPaddedHttpDataFrame (line 148) | @Test
method testHttpDataFrameReservedBits (line 176) | @Test
method testIllegalHttpDataFrameStreamId (line 191) | @Test
method testIllegalHttpDataFrameLength (line 204) | @Test
method testIllegalHttpDataFramePaddingLength (line 217) | @Test
method testHttpHeadersFrame (line 231) | @Test
method testLastHttpHeadersFrame (line 255) | @Test
method testLastSegmentHttpHeadersFrame (line 271) | @Test
method testHttpHeadersFrameReservedBits (line 287) | @Test
method testInvalidHttpHeadersFrame (line 304) | @Test
method testIllegalHttpHeadersFrame (line 317) | @Test
method testContinuedHttpHeadersFrame (line 330) | @Test
method testHttpHeadersFrameEmptyContinuation (line 356) | @Test
method testHttpHeadersFrameMultipleContinuations (line 376) | @Test
method testHttpHeadersFrameContinuationReservedFlags (line 403) | @Test
method testHttpHeadersFrameContinuationIllegalStreamId (line 422) | @Test
method testHttpHeadersFrameMissingContinuation (line 443) | @Test
method testHttpPriorityFrame (line 464) | @Test
method testHttpPriorityFrameReservedBits (line 481) | @Test
method testInvalidHttpPriorityFrame (line 499) | @Test
method testIllegalHttpPriorityFrame (line 516) | @Test
method testHttpRstStreamFrame (line 533) | @Test
method testHttpRstStreamFrameReservedBits (line 548) | @Test
method testInvalidHttpRstStreamFrame (line 564) | @Test
method testIllegalHttpRstStreamFrame (line 579) | @Test
method testHttpSettingsFrame (line 594) | @Test
method testHttpSettingsAckFrame (line 614) | @Test
method testHttpSettingsFrameWithMultiples (line 629) | @Test
method testHttpSettingsFrameReservedBits (line 653) | @Test
method testInvalidHttpSettingsFrame (line 674) | @Test
method testInvalidHttpSettingsAckFrame (line 691) | @Test
method testIllegalHttpSettingsFrame (line 708) | @Test
method testIllegalHttpSettingsAckFrame (line 725) | @Test
method testHttpPushPromiseFrame (line 738) | @Test
method testHttpPushPromiseFrameReservedBits (line 759) | @Test
method testIllegalHttpPushPromiseFrame (line 777) | @Test
method testContinuedHttpPushPromiseFrame (line 792) | @Test
method testHttpPushPromiseFrameEmptyContinuation (line 815) | @Test
method testHttpPushPromiseFrameMultipleContinuations (line 837) | @Test
method testHttpPushPromiseFrameContinuationReservedFlags (line 861) | @Test
method testHttpPushPromiseFrameContinuationIllegalStreamId (line 883) | @Test
method testHttpPushPromiseFrameMissingContinuation (line 906) | @Test
method testHttpPingFrame (line 924) | @Test
method testHttpPongFrame (line 939) | @Test
method testHttpPingFrameReservedBits (line 954) | @Test
method testInvalidHttpPingFrame (line 969) | @Test
method testIllegalHttpPingFrame (line 984) | @Test
method testHttpGoAwayFrame (line 999) | @Test
method testHttpGoAwayFrameReservedBits (line 1016) | @Test
method testHttpGoAwayFrameWithDebugData (line 1034) | @Test
method testInvalidHttpGoAwayFrame (line 1053) | @Test
method testIllegalHttpGoAwayFrame (line 1068) | @Test
method testHttpWindowUpdateFrame (line 1085) | @Test
method testHttpWindowUpdateFrameReservedBits (line 1100) | @Test
method testInvalidHttpWindowUpdateFrame (line 1116) | @Test
method testIllegalHttpWindowUpdateFrame (line 1131) | @Test
method testIllegalContinuationFrame (line 1146) | @Test
method testUnknownFrame (line 1159) | @Test
method frame (line 1172) | private ByteBuf frame(int length, int type, byte flags, int streamId) {
method dataFrame (line 1183) | private ByteBuf dataFrame(int length, byte flags, int streamId) {
method headersFrame (line 1188) | private ByteBuf headersFrame(int length, byte flags, int streamId) {
method priorityFrame (line 1193) | private ByteBuf priorityFrame(int length, byte flags, int streamId) {
method rstStreamFrame (line 1198) | private ByteBuf rstStreamFrame(int length, byte flags, int streamId) {
method settingsFrame (line 1203) | private ByteBuf settingsFrame(int length, byte flags, int streamId) {
method pushPromiseFrame (line 1208) | private ByteBuf pushPromiseFrame(int length, byte flags, int streamId) {
method pingFrame (line 1213) | private ByteBuf pingFrame(int length, byte flags, int streamId) {
method goAwayFrame (line 1218) | private ByteBuf goAwayFrame(int length, byte flags, int streamId) {
method windowUpdateFrame (line 1223) | private ByteBuf windowUpdateFrame(int length, byte flags, int streamId) {
method continuationFrame (line 1228) | private ByteBuf continuationFrame(int length, byte flags, int streamId) {
method setReservedBits (line 1233) | private void setReservedBits(ByteBuf frame) {
method writeRandomData (line 1237) | private void writeRandomData(ByteBuf frame, int length) {
method writePriorityFields (line 1243) | private void writePriorityFields(ByteBuf frame, boolean exclusive, int...
FILE: src/test/java/com/twitter/http2/HttpFrameEncoderTest.java
class HttpFrameEncoderTest (line 33) | public class HttpFrameEncoderTest {
method testHttpDataFrame (line 39) | @Test
method testEmptyHttpDataFrame (line 52) | @Test
method testLastHttpDataFrame (line 61) | @Test
method testHttpHeadersFrame (line 70) | @Test
method testEmptyHttpHeadersFrame (line 87) | @Test
method testLastHttpHeadersFrame (line 101) | @Test
method testContinuedHttpHeadersFrame (line 115) | @Test
method testHttpPriorityFrame (line 133) | @Test
method testHttpRstStreamFrame (line 145) | @Test
method testHttpSettingsFrame (line 155) | @Test
method testHttpSettingsAckFrame (line 167) | @Test
method testHttpPushPromiseFrame (line 177) | @Test
method testEmptyHttpPushPromiseFrame (line 191) | @Test
method testContinuedHttpPushPromiseFrame (line 201) | @Test
method testHttpPingFrame (line 215) | @Test
method testHttpPongFrame (line 224) | @Test
method testHttpGoAwayFrame (line 233) | @Test
method testHttpWindowUpdateFrame (line 243) | @Test
method assertDataFrame (line 253) | private static void assertDataFrame(ByteBuf frame, int streamId, boole...
method assertHeadersFrame (line 267) | private static void assertHeadersFrame(
method assertPriorityFrame (line 320) | private static void assertPriorityFrame(
method assertRstStreamFrame (line 335) | private static void assertRstStreamFrame(ByteBuf frame, int streamId, ...
method assertSettingsFrame (line 343) | private static void assertSettingsFrame(ByteBuf frame, boolean ack, in...
method assertPushPromiseFrame (line 357) | private static void assertPushPromiseFrame(
method assertPingFrame (line 388) | private static void assertPingFrame(ByteBuf frame, boolean pong, long ...
method assertGoAwayFrame (line 399) | private static void assertGoAwayFrame(ByteBuf frame, int lastStreamId,...
method assertWindowUpdateFrame (line 408) | private static void assertWindowUpdateFrame(
method assertFrameHeader (line 419) | private static int assertFrameHeader(ByteBuf frame, byte type, byte fl...
FILE: src/test/java/com/twitter/http2/HttpHeaderCompressionTest.java
class HttpHeaderCompressionTest (line 41) | public class HttpHeaderCompressionTest {
method testHttpHeadersFrame (line 43) | @Test
method testHeaderEcho (line 50) | private void testHeaderEcho(HttpHeaderBlockFrame frame) throws Throwab...
class EchoHandler (line 111) | private static class EchoHandler extends ChannelInboundHandlerAdapter {
method channelRead (line 114) | @Override
method exceptionCaught (line 119) | @Override
class TestHandler (line 127) | private static class TestHandler extends ChannelInboundHandlerAdapter {
method TestHandler (line 136) | public TestHandler(HttpHeaderBlockFrame frame) {
method channelActive (line 140) | @Override
method channelRead (line 146) | @Override
method exceptionCaught (line 163) | @Override
FILE: src/test/java/com/twitter/http2/HttpRequestProxyTest.java
class HttpRequestProxyTest (line 27) | public class HttpRequestProxyTest {
method testHttpRequest (line 33) | @Test
method testGetMethod (line 38) | @Test
method testSetMethod (line 43) | @Test
method testGetUri (line 49) | @Test
method testSetUri (line 54) | @Test
method testToString (line 60) | @Test
method testGetProtocolVersion (line 65) | @Test
method testSetProtocolVersion (line 70) | @Test
method testHeaders (line 76) | @Test
FILE: src/test/java/com/twitter/http2/HttpResponseProxyTest.java
class HttpResponseProxyTest (line 27) | public class HttpResponseProxyTest {
method testSetStatus (line 33) | @Test
method testGetStatus (line 39) | @Test
method testToString (line 44) | @Test
method testGetProtocolVersion (line 49) | @Test
method testSetProtocolVersion (line 54) | @Test
method testHeaders (line 60) | @Test
FILE: src/test/java/com/twitter/http2/PipeTest.java
class PipeTest (line 28) | public class PipeTest {
method createPipe (line 51) | @Before
method testS (line 56) | @Test
method testR (line 62) | @Test
method testSR (line 68) | @Test
method testRS (line 78) | @Test
method testSS (line 88) | @Test
method testRR (line 98) | @Test
method testSRR (line 107) | @Test
method testSSR (line 122) | @Test
method testSRS (line 137) | @Test
method testRSR (line 152) | @Test
method testRSS (line 167) | @Test
method testRRS (line 182) | @Test
method testSSRR (line 197) | @Test
method testRRSS (line 218) | @Test
method testRSSR (line 239) | @Test
method testSRRS (line 260) | @Test
method testSSRRR (line 281) | @Test
method testCS (line 308) | @Test
method testSC (line 316) | @Test
method testCR (line 325) | @Test
method testRC (line 333) | @Test
method testCSR (line 342) | @Test
method testSCR (line 354) | @Test
method testSRCS (line 367) | @Test
method testSRCR (line 382) | @Test
method testCRS (line 398) | @Test
method testRCS (line 410) | @Test
method testRSCR (line 423) | @Test
method testRSCS (line 440) | @Test
Condensed preview — 54 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (324K chars).
[
{
"path": ".gitignore",
"chars": 19,
"preview": "target\n.idea\n*.iml\n"
},
{
"path": ".travis.yml",
"chars": 114,
"preview": "language: java\nsudo: false\njdk:\n - openjdk7\nafter_success:\n - mvn clean cobertura:cobertura coveralls:cobertura\n"
},
{
"path": "LICENSE",
"chars": 11325,
"preview": "Apache License\n Version 2.0, January 2004\n http://www.apache.org/licens"
},
{
"path": "README.md",
"chars": 251,
"preview": "# netty-http2 [](https://travis-ci.org/twitter/netty-http2"
},
{
"path": "pom.xml",
"chars": 7386,
"preview": "<project xmlns=\"https://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLoc"
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttp2Headers.java",
"chars": 1995,
"preview": "package com.twitter.http2;\n\nimport io.netty.handler.codec.DefaultHeaders.NameValidator;\nimport io.netty.handler.codec.ht"
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpDataFrame.java",
"chars": 4200,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpGoAwayFrame.java",
"chars": 2560,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpHeaderBlockFrame.java",
"chars": 2040,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpHeadersFrame.java",
"chars": 3768,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpPingFrame.java",
"chars": 1772,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpPriorityFrame.java",
"chars": 3356,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpPushPromiseFrame.java",
"chars": 2656,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpRstStreamFrame.java",
"chars": 2510,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpSettingsFrame.java",
"chars": 2821,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpStreamFrame.java",
"chars": 1362,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/DefaultHttpWindowUpdateFrame.java",
"chars": 2542,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpCodecUtil.java",
"chars": 4031,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpConnection.java",
"chars": 14207,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpConnectionHandler.java",
"chars": 46013,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpDataFrame.java",
"chars": 1574,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpErrorCode.java",
"chars": 4923,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpFrame.java",
"chars": 704,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpFrameDecoder.java",
"chars": 20769,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpFrameDecoderDelegate.java",
"chars": 3095,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpFrameEncoder.java",
"chars": 9376,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpGoAwayFrame.java",
"chars": 1179,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpHeaderBlockDecoder.java",
"chars": 5093,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpHeaderBlockEncoder.java",
"chars": 5033,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpHeaderBlockFrame.java",
"chars": 1349,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpHeadersFrame.java",
"chars": 1921,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpMessageProxy.java",
"chars": 1853,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpPingFrame.java",
"chars": 1110,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpPriorityFrame.java",
"chars": 1728,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpProtocolException.java",
"chars": 1200,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpPushPromiseFrame.java",
"chars": 1191,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpRequestProxy.java",
"chars": 1970,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpResponseProxy.java",
"chars": 1667,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpRstStreamFrame.java",
"chars": 1187,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpSettingsFrame.java",
"chars": 1872,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpStreamDecoder.java",
"chars": 11481,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpStreamEncoder.java",
"chars": 14263,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpStreamFrame.java",
"chars": 998,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/HttpWindowUpdateFrame.java",
"chars": 1280,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/Pipe.java",
"chars": 4774,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/StreamedHttpMessage.java",
"chars": 2067,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/StreamedHttpRequest.java",
"chars": 1744,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/main/java/com/twitter/http2/StreamedHttpResponse.java",
"chars": 1754,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/test/java/com/twitter/http2/HttpFrameDecoderTest.java",
"chars": 47329,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/test/java/com/twitter/http2/HttpFrameEncoderTest.java",
"chars": 16214,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/test/java/com/twitter/http2/HttpHeaderCompressionTest.java",
"chars": 6371,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/test/java/com/twitter/http2/HttpRequestProxyTest.java",
"chars": 2595,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/test/java/com/twitter/http2/HttpResponseProxyTest.java",
"chars": 2216,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
},
{
"path": "src/test/java/com/twitter/http2/PipeTest.java",
"chars": 13813,
"preview": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not "
}
]
About this extraction
This page contains the full source code of the twitter/netty-http2 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 54 files (303.3 KB), approximately 67.2k tokens, and a symbol index with 589 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.