SPDY Protocol Framing Layer
This commit is contained in:
parent
e4fa180478
commit
cf8a4d627d
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyDataFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyDataFrame implements SpdyDataFrame {
|
||||
|
||||
private int streamID;
|
||||
private boolean last;
|
||||
private boolean compressed;
|
||||
private ChannelBuffer data = ChannelBuffers.EMPTY_BUFFER;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamID the Stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyDataFrame(int streamID) {
|
||||
setStreamID(streamID);
|
||||
}
|
||||
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
public void setLast(boolean last) {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
public boolean isCompressed() {
|
||||
return compressed;
|
||||
}
|
||||
|
||||
public void setCompressed(boolean compressed) {
|
||||
this.compressed = compressed;
|
||||
}
|
||||
|
||||
public ChannelBuffer getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(ChannelBuffer data) {
|
||||
if (data == null) {
|
||||
data = ChannelBuffers.EMPTY_BUFFER;
|
||||
}
|
||||
if (data.readableBytes() > SpdyCodecUtil.SPDY_MAX_LENGTH) {
|
||||
throw new IllegalArgumentException("data payload cannot exceed "
|
||||
+ SpdyCodecUtil.SPDY_MAX_LENGTH + " bytes");
|
||||
}
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append("(last: ");
|
||||
buf.append(isLast());
|
||||
buf.append("; compressed: ");
|
||||
buf.append(isCompressed());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Stream-ID = ");
|
||||
buf.append(streamID);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Size = ");
|
||||
buf.append(data.readableBytes());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyGoAwayFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
|
||||
|
||||
private int lastGoodStreamID;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param lastGoodStreamID the Last-good-stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyGoAwayFrame(int lastGoodStreamID) {
|
||||
setLastGoodStreamID(lastGoodStreamID);
|
||||
}
|
||||
|
||||
public int getLastGoodStreamID() {
|
||||
return lastGoodStreamID;
|
||||
}
|
||||
|
||||
public void setLastGoodStreamID(int lastGoodStreamID) {
|
||||
if (lastGoodStreamID < 0) {
|
||||
throw new IllegalArgumentException("Last-good-stream-ID"
|
||||
+ " cannot be negative: " + lastGoodStreamID);
|
||||
}
|
||||
this.lastGoodStreamID = lastGoodStreamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Last-good-stream-ID = ");
|
||||
buf.append(lastGoodStreamID);
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyHeaderBlock} implementation.
|
||||
*/
|
||||
public class DefaultSpdyHeaderBlock implements SpdyHeaderBlock {
|
||||
|
||||
private boolean invalid;
|
||||
private final SpdyHeaders headers = new SpdyHeaders();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
protected DefaultSpdyHeaderBlock() {
|
||||
}
|
||||
|
||||
public boolean isInvalid() {
|
||||
return invalid;
|
||||
}
|
||||
|
||||
public void setInvalid() {
|
||||
this.invalid = true;
|
||||
}
|
||||
|
||||
public void addHeader(final String name, final Object value) {
|
||||
headers.addHeader(name, value);
|
||||
}
|
||||
|
||||
public void setHeader(final String name, final Object value) {
|
||||
headers.setHeader(name, value);
|
||||
}
|
||||
|
||||
public void setHeader(final String name, final Iterable<?> values) {
|
||||
headers.setHeader(name, values);
|
||||
}
|
||||
|
||||
public void removeHeader(final String name) {
|
||||
headers.removeHeader(name);
|
||||
}
|
||||
|
||||
public void clearHeaders() {
|
||||
headers.clearHeaders();
|
||||
}
|
||||
|
||||
public String getHeader(final String name) {
|
||||
List<String> values = getHeaders(name);
|
||||
return values.size() > 0 ? values.get(0) : null;
|
||||
}
|
||||
|
||||
public List<String> getHeaders(final String name) {
|
||||
return headers.getHeaders(name);
|
||||
}
|
||||
|
||||
public List<Map.Entry<String, String>> getHeaders() {
|
||||
return headers.getHeaders();
|
||||
}
|
||||
|
||||
public boolean containsHeader(final String name) {
|
||||
return headers.containsHeader(name);
|
||||
}
|
||||
|
||||
public Set<String> getHeaderNames() {
|
||||
return headers.getHeaderNames();
|
||||
}
|
||||
|
||||
protected void appendHeaders(StringBuilder buf) {
|
||||
for (Map.Entry<String, String> e: getHeaders()) {
|
||||
buf.append(" ");
|
||||
buf.append(e.getKey());
|
||||
buf.append(": ");
|
||||
buf.append(e.getValue());
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyHeadersFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock
|
||||
implements SpdyHeadersFrame {
|
||||
|
||||
private int streamID;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamID the Stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyHeadersFrame(int streamID) {
|
||||
super();
|
||||
setStreamID(streamID);
|
||||
}
|
||||
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Stream-ID = ");
|
||||
buf.append(streamID);
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyNoOpFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyNoOpFrame implements SpdyNoOpFrame {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public DefaultSpdyNoOpFrame() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyPingFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyPingFrame implements SpdyPingFrame {
|
||||
|
||||
private int ID;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param ID the unique ID of this frame
|
||||
*/
|
||||
public DefaultSpdyPingFrame(int ID) {
|
||||
setID(ID);
|
||||
}
|
||||
|
||||
public int getID() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
public void setID(int ID) {
|
||||
this.ID = ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> ID = ");
|
||||
buf.append(ID);
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyRstStreamFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyRstStreamFrame implements SpdyRstStreamFrame {
|
||||
|
||||
private int streamID;
|
||||
private SpdyStreamStatus status;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamID the Stream-ID of this frame
|
||||
* @param statusCode the Status code of this frame
|
||||
*/
|
||||
public DefaultSpdyRstStreamFrame(int streamID, int statusCode) {
|
||||
this(streamID, SpdyStreamStatus.valueOf(statusCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamID the Stream-ID of this frame
|
||||
* @param status the status of this frame
|
||||
*/
|
||||
public DefaultSpdyRstStreamFrame(int streamID, SpdyStreamStatus status) {
|
||||
setStreamID(streamID);
|
||||
setStatus(status);
|
||||
}
|
||||
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
public SpdyStreamStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(SpdyStreamStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Stream-ID = ");
|
||||
buf.append(streamID);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Status: ");
|
||||
buf.append(status.toString());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdySettingsFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdySettingsFrame implements SpdySettingsFrame {
|
||||
|
||||
private boolean clear;
|
||||
private final Map<Integer, Setting> settingsMap = new TreeMap<Integer, Setting>();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public DefaultSpdySettingsFrame() {
|
||||
}
|
||||
|
||||
public Set<Integer> getIDs() {
|
||||
return settingsMap.keySet();
|
||||
}
|
||||
|
||||
public boolean isSet(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
return settingsMap.containsKey(key);
|
||||
}
|
||||
|
||||
public int getValue(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
return settingsMap.get(key).getValue();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void setValue(int ID, int value) {
|
||||
setValue(ID, value, false, false);
|
||||
}
|
||||
|
||||
public void setValue(int ID, int value, boolean persistValue, boolean persisted) {
|
||||
if (ID <= 0 || ID > SpdyCodecUtil.SPDY_SETTINGS_MAX_ID) {
|
||||
throw new IllegalArgumentException("Setting ID is not valid: " + ID);
|
||||
}
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
Setting setting = settingsMap.get(key);
|
||||
setting.setValue(value);
|
||||
setting.setPersist(persistValue);
|
||||
setting.setPersisted(persisted);
|
||||
} else {
|
||||
settingsMap.put(key, new Setting(value, persistValue, persisted));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeValue(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
settingsMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean persistValue(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
return settingsMap.get(key).getPersist();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPersistValue(int ID, boolean persistValue) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
settingsMap.get(key).setPersist(persistValue);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPersisted(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
return settingsMap.get(key).getPersisted();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPersisted(int ID, boolean persisted) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
settingsMap.get(key).setPersisted(persisted);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean clearPreviouslyPersistedSettings() {
|
||||
return clear;
|
||||
}
|
||||
|
||||
public void setClearPreviouslyPersistedSettings(boolean clear) {
|
||||
this.clear = clear;
|
||||
}
|
||||
|
||||
private Set<Map.Entry<Integer, Setting>> getSettings() {
|
||||
return settingsMap.entrySet();
|
||||
}
|
||||
|
||||
private void appendSettings(StringBuilder buf) {
|
||||
for (Map.Entry<Integer, Setting> e: getSettings()) {
|
||||
Setting setting = e.getValue();
|
||||
buf.append("--> ");
|
||||
buf.append(e.getKey().toString());
|
||||
buf.append(":");
|
||||
buf.append(setting.getValue());
|
||||
buf.append(" (persist value: ");
|
||||
buf.append(setting.getPersist());
|
||||
buf.append("; persisted: ");
|
||||
buf.append(setting.getPersisted());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
appendSettings(buf);
|
||||
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final class Setting {
|
||||
|
||||
private int value;
|
||||
private boolean persist;
|
||||
private boolean persisted;
|
||||
|
||||
public Setting(int value, boolean persist, boolean persisted) {
|
||||
this.value = value;
|
||||
this.persist = persist;
|
||||
this.persisted = persisted;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean getPersist() {
|
||||
return persist;
|
||||
}
|
||||
|
||||
public void setPersist(boolean persist) {
|
||||
this.persist = persist;
|
||||
}
|
||||
|
||||
public boolean getPersisted() {
|
||||
return persisted;
|
||||
}
|
||||
|
||||
public void setPersisted(boolean persisted) {
|
||||
this.persisted = persisted;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdySynReplyFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdySynReplyFrame extends DefaultSpdyHeaderBlock
|
||||
implements SpdySynReplyFrame {
|
||||
|
||||
private int streamID;
|
||||
private boolean last;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamID the Stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdySynReplyFrame(int streamID) {
|
||||
super();
|
||||
setStreamID(streamID);
|
||||
}
|
||||
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
public void setLast(boolean last) {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append("(last: ");
|
||||
buf.append(isLast());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Stream-ID = ");
|
||||
buf.append(streamID);
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdySynStreamFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdySynStreamFrame extends DefaultSpdyHeaderBlock
|
||||
implements SpdySynStreamFrame {
|
||||
|
||||
private int streamID;
|
||||
private int associatedToStreamID;
|
||||
private byte priority;
|
||||
private boolean last;
|
||||
private boolean unidirectional;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamID the Stream-ID of this frame
|
||||
* @param associatedToStreamID the Associated-To-Stream-ID of this frame
|
||||
* @param priority the priority of the stream
|
||||
*/
|
||||
public DefaultSpdySynStreamFrame(
|
||||
int streamID, int associatedToStreamID, byte priority) {
|
||||
super();
|
||||
setStreamID(streamID);
|
||||
setAssociatedToStreamID(associatedToStreamID);
|
||||
setPriority(priority);
|
||||
}
|
||||
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
public int getAssociatedToStreamID() {
|
||||
return associatedToStreamID;
|
||||
}
|
||||
|
||||
public void setAssociatedToStreamID(int associatedToStreamID) {
|
||||
if (associatedToStreamID < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Associated-To-Stream-ID cannot be negative: " +
|
||||
associatedToStreamID);
|
||||
}
|
||||
this.associatedToStreamID = associatedToStreamID;
|
||||
}
|
||||
|
||||
public byte getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(byte priority) {
|
||||
if (priority < 0 || priority > 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"Priortiy must be between 0 and 3 inclusive: " + priority);
|
||||
}
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
public void setLast(boolean last) {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
public boolean isUnidirectional() {
|
||||
return unidirectional;
|
||||
}
|
||||
|
||||
public void setUnidirectional(boolean unidirectional) {
|
||||
this.unidirectional = unidirectional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append("(last: ");
|
||||
buf.append(isLast());
|
||||
buf.append("; unidirectional: ");
|
||||
buf.append(isUnidirectional());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Stream-ID = ");
|
||||
buf.append(streamID);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
if (associatedToStreamID != 0) {
|
||||
buf.append("--> Associated-To-Stream-ID = ");
|
||||
buf.append(associatedToStreamID);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
}
|
||||
buf.append("--> Priority = ");
|
||||
buf.append(priority);
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.Formatter;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
final class SpdyCodecUtil {
|
||||
|
||||
static final int SPDY_VERSION = 2;
|
||||
|
||||
static final int SPDY_HEADER_TYPE_OFFSET = 2;
|
||||
static final int SPDY_HEADER_FLAGS_OFFSET = 4;
|
||||
static final int SPDY_HEADER_LENGTH_OFFSET = 5;
|
||||
static final int SPDY_HEADER_SIZE = 8;
|
||||
|
||||
static final int SPDY_MAX_LENGTH = 0xFFFFFF; // Length is a 24-bit field
|
||||
|
||||
static final byte SPDY_DATA_FLAG_FIN = 0x01;
|
||||
static final byte SPDY_DATA_FLAG_COMPRESS = 0x02;
|
||||
|
||||
static final int SPDY_SYN_STREAM_FRAME = 1;
|
||||
static final int SPDY_SYN_REPLY_FRAME = 2;
|
||||
static final int SPDY_RST_STREAM_FRAME = 3;
|
||||
static final int SPDY_SETTINGS_FRAME = 4;
|
||||
static final int SPDY_NOOP_FRAME = 5;
|
||||
static final int SPDY_PING_FRAME = 6;
|
||||
static final int SPDY_GOAWAY_FRAME = 7;
|
||||
static final int SPDY_HEADERS_FRAME = 8;
|
||||
static final int SPDY_WINDOW_UPDATE_FRAME = 9;
|
||||
|
||||
static final byte SPDY_FLAG_FIN = 0x01;
|
||||
static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02;
|
||||
|
||||
static final byte SPDY_SETTINGS_CLEAR = 0x01;
|
||||
static final byte SPDY_SETTINGS_PERSIST_VALUE = 0x01;
|
||||
static final byte SPDY_SETTINGS_PERSISTED = 0x02;
|
||||
|
||||
static final int SPDY_SETTINGS_MAX_ID = 0xFFFFFF; // ID is a 24-bit field
|
||||
|
||||
static final int SPDY_MAX_NV_LENGTH = 0xFFFF; // Length is a 16-bit field
|
||||
|
||||
// Zlib Dictionary
|
||||
private static final String SPDY_DICT_S =
|
||||
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" +
|
||||
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" +
|
||||
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" +
|
||||
"-agent10010120020120220320420520630030130230330430530630740040140240340440" +
|
||||
"5406407408409410411412413414415416417500501502503504505accept-rangesageeta" +
|
||||
"glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" +
|
||||
"ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" +
|
||||
"sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" +
|
||||
"oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" +
|
||||
"ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" +
|
||||
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" +
|
||||
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" +
|
||||
".1statusversionurl ";
|
||||
static final byte[] SPDY_DICT;
|
||||
static {
|
||||
byte[] SPDY_DICT_ = null;
|
||||
|
||||
try {
|
||||
SPDY_DICT_ = SPDY_DICT_S.getBytes("US-ASCII");
|
||||
// dictionary is null terminated
|
||||
SPDY_DICT_[SPDY_DICT_.length - 1] = (byte) 0;
|
||||
} catch (Exception e) {
|
||||
SPDY_DICT_ = new byte[1];
|
||||
}
|
||||
|
||||
SPDY_DICT = SPDY_DICT_;
|
||||
}
|
||||
|
||||
|
||||
private SpdyCodecUtil() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a big-endian unsigned short integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedShort(ChannelBuffer buf, int offset) {
|
||||
return (int) ((buf.getByte(offset) & 0xFF) << 8 |
|
||||
(buf.getByte(offset + 1) & 0xFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian unsigned medium integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedMedium(ChannelBuffer buf, int offset) {
|
||||
return (int) ((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(ChannelBuffer buf, int offset) {
|
||||
return (int) ((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(ChannelBuffer buf, int offset) {
|
||||
return (int) ((buf.getByte(offset) & 0xFF) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
(buf.getByte(offset + 3) & 0xFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a SPDY header name.
|
||||
*/
|
||||
static void validateHeaderName(String name) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
if (name.length() == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"name cannot be length zero");
|
||||
}
|
||||
// Since name may only contain ascii characters, for valid names
|
||||
// name.length() returns the number of bytes when UTF-8 encoded.
|
||||
if (name.length() > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"name exceeds allowable length: " + name);
|
||||
}
|
||||
for (int i = 0; i < name.length(); i ++) {
|
||||
char c = name.charAt(i);
|
||||
if (c == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"name contains null character: " + name);
|
||||
}
|
||||
if (c > 127) {
|
||||
throw new IllegalArgumentException(
|
||||
"name contains non-ascii character: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a SPDY header value. Does not validate max length.
|
||||
*/
|
||||
static void validateHeaderValue(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
if (value.length() == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"value cannot be length zero");
|
||||
}
|
||||
for (int i = 0; i < value.length(); i ++) {
|
||||
char c = value.charAt(i);
|
||||
if (c == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"value contains null character: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol Data Frame
|
||||
*/
|
||||
public interface SpdyDataFrame {
|
||||
|
||||
/**
|
||||
* Returns the Stream-ID of this frame.
|
||||
*/
|
||||
int getStreamID();
|
||||
|
||||
/**
|
||||
* Sets the Stream-ID of this frame. The Stream-ID must be positive.
|
||||
*/
|
||||
void setStreamID(int streamID);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void setLast(boolean last);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the data in this frame has been compressed.
|
||||
*/
|
||||
boolean isCompressed();
|
||||
|
||||
/**
|
||||
* Sets if the data in this frame has been compressed.
|
||||
*/
|
||||
void setCompressed(boolean compressed);
|
||||
|
||||
/**
|
||||
* Returns the data payload of this frame. If there is no data payload
|
||||
* {@link ChannelBuffers#EMPTY_BUFFER} is returned.
|
||||
*/
|
||||
ChannelBuffer getData();
|
||||
|
||||
/**
|
||||
* Sets the data payload of this frame. If {@code null} is specified,
|
||||
* the data payload will be set to {@link ChannelBuffers#EMPTY_BUFFER}.
|
||||
* The data payload cannot exceed 16777215 bytes.
|
||||
*/
|
||||
void setData(ChannelBuffer data);
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import org.jboss.netty.channel.ChannelDownstreamHandler;
|
||||
import org.jboss.netty.channel.ChannelEvent;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelUpstreamHandler;
|
||||
|
||||
/**
|
||||
* A combination of {@link SpdyFrameDecoder} and {@link SpdyFrameEncoder}.
|
||||
* @apiviz.has org.jboss.netty.handler.codec.spdy.SpdyFrameDecoder
|
||||
* @apiviz.has org.jboss.netty.handler.codec.spdy.SpdyFrameEncoder
|
||||
*/
|
||||
public class SpdyFrameCodec implements ChannelUpstreamHandler,
|
||||
ChannelDownstreamHandler {
|
||||
|
||||
private final SpdyFrameDecoder decoder = new SpdyFrameDecoder();
|
||||
private final SpdyFrameEncoder encoder = new SpdyFrameEncoder();
|
||||
|
||||
public SpdyFrameCodec() {
|
||||
}
|
||||
|
||||
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
|
||||
throws Exception {
|
||||
decoder.handleUpstream(ctx, e);
|
||||
}
|
||||
|
||||
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)
|
||||
throws Exception {
|
||||
encoder.handleDownstream(ctx, e);
|
||||
}
|
||||
}
|
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.compression.ZlibDecoder;
|
||||
import org.jboss.netty.handler.codec.embedder.DecoderEmbedder;
|
||||
import org.jboss.netty.handler.codec.frame.FrameDecoder;
|
||||
|
||||
import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
/**
|
||||
* Decodes {@link ChannelBuffer}s into SPDY Data and Control Frames.
|
||||
*/
|
||||
public class SpdyFrameDecoder extends FrameDecoder {
|
||||
|
||||
private final DecoderEmbedder<ChannelBuffer> headerBlockDecompressor =
|
||||
new DecoderEmbedder<ChannelBuffer>(new ZlibDecoder(SPDY_DICT));
|
||||
|
||||
public SpdyFrameDecoder() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object decode(
|
||||
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)
|
||||
throws Exception {
|
||||
|
||||
// Must read common header to determine frame length
|
||||
if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get frame length from common header
|
||||
int frameOffset = buffer.readerIndex();
|
||||
int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
|
||||
int dataLength = getUnsignedMedium(buffer, lengthOffset);
|
||||
int frameLength = SPDY_HEADER_SIZE + dataLength;
|
||||
|
||||
// Wait until entire frame is readable
|
||||
if (buffer.readableBytes() < frameLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read common header fields
|
||||
boolean control = (buffer.getByte(frameOffset) & 0x80) != 0;
|
||||
int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
|
||||
byte flags = buffer.getByte(flagsOffset);
|
||||
|
||||
if (control) {
|
||||
// Decode control frame common header
|
||||
int version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
|
||||
|
||||
// Spdy versioning spec is broken
|
||||
if (version != SPDY_VERSION) {
|
||||
buffer.skipBytes(frameLength);
|
||||
throw new SpdyProtocolException(
|
||||
"Unsupported version: " + version);
|
||||
}
|
||||
|
||||
int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET;
|
||||
int type = getUnsignedShort(buffer, typeOffset);
|
||||
buffer.skipBytes(SPDY_HEADER_SIZE);
|
||||
|
||||
return decodeControlFrame(type, flags, buffer.readBytes(dataLength));
|
||||
} else {
|
||||
// Decode data frame common header
|
||||
int streamID = getUnsignedInt(buffer, frameOffset);
|
||||
buffer.skipBytes(SPDY_HEADER_SIZE);
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
|
||||
spdyDataFrame.setCompressed((flags & SPDY_DATA_FLAG_COMPRESS) != 0);
|
||||
spdyDataFrame.setData(buffer.readBytes(dataLength));
|
||||
|
||||
return spdyDataFrame;
|
||||
}
|
||||
}
|
||||
|
||||
private Object decodeControlFrame(int type, byte flags, ChannelBuffer data)
|
||||
throws Exception {
|
||||
int streamID;
|
||||
boolean last;
|
||||
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
if (data.readableBytes() < 12) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid SYN_STREAM control frame");
|
||||
}
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
int associatedToStreamID = getUnsignedInt(data, data.readerIndex() + 4);
|
||||
byte priority = (byte) (data.getByte(data.readerIndex() + 8) >> 6 & 0x03);
|
||||
data.skipBytes(10);
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
|
||||
|
||||
last = (flags & SPDY_FLAG_FIN) != 0;
|
||||
boolean unid = (flags & SPDY_FLAG_UNIDIRECTIONAL) != 0;
|
||||
spdySynStreamFrame.setLast(last);
|
||||
spdySynStreamFrame.setUnidirectional(unid);
|
||||
|
||||
decodeHeaderBlock(spdySynStreamFrame, decompress(data));
|
||||
|
||||
return spdySynStreamFrame;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
if (data.readableBytes() < 8) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid SYN_REPLY control frame");
|
||||
}
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
data.skipBytes(6);
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame =
|
||||
new DefaultSpdySynReplyFrame(streamID);
|
||||
|
||||
last = (flags & SPDY_FLAG_FIN) != 0;
|
||||
spdySynReplyFrame.setLast(last);
|
||||
|
||||
decodeHeaderBlock(spdySynReplyFrame, decompress(data));
|
||||
|
||||
return spdySynReplyFrame;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
if (flags != 0 || data.readableBytes() != 8) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid RST_STREAM control frame");
|
||||
}
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
int statusCode = getSignedInt(data, data.readerIndex() + 4);
|
||||
if (statusCode == 0) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid RST_STREAM status code");
|
||||
}
|
||||
|
||||
return new DefaultSpdyRstStreamFrame(streamID, statusCode);
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
if (data.readableBytes() < 4) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid SETTINGS control frame");
|
||||
}
|
||||
// Each ID/Value entry is 8 bytes
|
||||
// The number of entries cannot exceed SPDY_MAX_LENGTH / 8;
|
||||
int numEntries = getUnsignedInt(data, data.readerIndex());
|
||||
if ((numEntries > (SPDY_MAX_LENGTH - 4) / 8) ||
|
||||
(data.readableBytes() != numEntries * 8 + 4)) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid SETTINGS control frame");
|
||||
}
|
||||
data.skipBytes(4);
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = new DefaultSpdySettingsFrame();
|
||||
|
||||
boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0;
|
||||
spdySettingsFrame.setClearPreviouslyPersistedSettings(clear);
|
||||
|
||||
for (int i = 0; i < numEntries; i ++) {
|
||||
// Chromium Issue 79156
|
||||
// SPDY setting ids are not written in network byte order
|
||||
// Read id assuming the architecture is little endian
|
||||
int ID = (data.readByte() & 0xFF) |
|
||||
(data.readByte() & 0xFF) << 8 |
|
||||
(data.readByte() & 0xFF) << 16;
|
||||
byte ID_flags = data.readByte();
|
||||
int value = getSignedInt(data, data.readerIndex());
|
||||
data.skipBytes(4);
|
||||
|
||||
if (!(spdySettingsFrame.isSet(ID))) {
|
||||
boolean persistVal = (ID_flags & SPDY_SETTINGS_PERSIST_VALUE) != 0;
|
||||
boolean persisted = (ID_flags & SPDY_SETTINGS_PERSISTED) != 0;
|
||||
spdySettingsFrame.setValue(ID, value, persistVal, persisted);
|
||||
}
|
||||
}
|
||||
|
||||
return spdySettingsFrame;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
if (data.readableBytes() != 0) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid NOOP control frame");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
if (data.readableBytes() != 4) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid PING control frame");
|
||||
}
|
||||
int ID = getSignedInt(data, data.readerIndex());
|
||||
|
||||
return new DefaultSpdyPingFrame(ID);
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
if (data.readableBytes() != 4) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid GOAWAY control frame");
|
||||
}
|
||||
int lastGoodStreamID = getUnsignedInt(data, data.readerIndex());
|
||||
|
||||
return new DefaultSpdyGoAwayFrame(lastGoodStreamID);
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
// Protocol allows length 4 frame when there are no name/value pairs
|
||||
if (data.readableBytes() == 4) {
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
return new DefaultSpdyHeadersFrame(streamID);
|
||||
}
|
||||
|
||||
if (data.readableBytes() < 8) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid HEADERS control frame");
|
||||
}
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
data.skipBytes(6);
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
|
||||
|
||||
decodeHeaderBlock(spdyHeadersFrame, decompress(data));
|
||||
|
||||
return spdyHeadersFrame;
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ChannelBuffer decompress(ChannelBuffer compressed) throws Exception {
|
||||
headerBlockDecompressor.offer(compressed);
|
||||
return headerBlockDecompressor.poll();
|
||||
}
|
||||
|
||||
private void decodeHeaderBlock(SpdyHeaderBlock headerFrame, ChannelBuffer headerBlock)
|
||||
throws Exception {
|
||||
if (headerBlock.readableBytes() < 2) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
}
|
||||
int numEntries = getUnsignedShort(headerBlock, headerBlock.readerIndex());
|
||||
headerBlock.skipBytes(2);
|
||||
for (int i = 0; i < numEntries; i ++) {
|
||||
if (headerBlock.readableBytes() < 2) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
}
|
||||
int nameLength = getUnsignedShort(headerBlock, headerBlock.readerIndex());
|
||||
headerBlock.skipBytes(2);
|
||||
if (nameLength == 0) {
|
||||
headerFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
if (headerBlock.readableBytes() < nameLength) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
}
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
headerBlock.readBytes(nameBytes);
|
||||
String name = new String(nameBytes, "UTF-8");
|
||||
if (headerFrame.containsHeader(name)) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received duplicate header name: " + name);
|
||||
}
|
||||
if (headerBlock.readableBytes() < 2) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
}
|
||||
int valueLength = getUnsignedShort(headerBlock, headerBlock.readerIndex());
|
||||
headerBlock.skipBytes(2);
|
||||
if (valueLength == 0) {
|
||||
headerFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
if (headerBlock.readableBytes() < valueLength) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
}
|
||||
byte[] valueBytes = new byte[valueLength];
|
||||
headerBlock.readBytes(valueBytes);
|
||||
int index = 0;
|
||||
int offset = 0;
|
||||
while (index < valueBytes.length) {
|
||||
while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
|
||||
index ++;
|
||||
}
|
||||
if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
|
||||
// Received multiple, in-sequence NULL characters
|
||||
headerFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
String value = new String(valueBytes, offset, index - offset, "UTF-8");
|
||||
headerFrame.addHeader(name, value);
|
||||
index ++;
|
||||
offset = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.compression.ZlibEncoder;
|
||||
import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
/**
|
||||
* Encodes a SPDY Data or Control Frame into a {@link ChannelBuffer}.
|
||||
*/
|
||||
public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
|
||||
private final EncoderEmbedder<ChannelBuffer> headerBlockCompressor =
|
||||
new EncoderEmbedder<ChannelBuffer>(new ZlibEncoder(9, SPDY_DICT));
|
||||
|
||||
public SpdyFrameEncoder() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object encode(
|
||||
ChannelHandlerContext ctx, Channel channel, Object msg)
|
||||
throws Exception {
|
||||
|
||||
if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
ChannelBuffer data = spdyDataFrame.getData();
|
||||
byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0;
|
||||
if (spdyDataFrame.isCompressed()) {
|
||||
flags |= SPDY_DATA_FLAG_COMPRESS;
|
||||
}
|
||||
ChannelBuffer header = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE);
|
||||
header.writeInt(spdyDataFrame.getStreamID() & 0x7FFFFFFF);
|
||||
header.writeByte(flags);
|
||||
header.writeMedium(data.readableBytes());
|
||||
return ChannelBuffers.wrappedBuffer(header, data);
|
||||
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(spdySynStreamFrame));
|
||||
byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
if (spdySynStreamFrame.isUnidirectional()) {
|
||||
flags |= SPDY_FLAG_UNIDIRECTIONAL;
|
||||
}
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length = (headerBlockLength == 0) ? 12 : 10 + headerBlockLength;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(SPDY_SYN_STREAM_FRAME);
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(length);
|
||||
frame.writeInt(spdySynStreamFrame.getStreamID());
|
||||
frame.writeInt(spdySynStreamFrame.getAssociatedToStreamID());
|
||||
frame.writeShort(((short) spdySynStreamFrame.getPriority()) << 14);
|
||||
if (data.readableBytes() == 0) {
|
||||
frame.writeShort(0);
|
||||
}
|
||||
return ChannelBuffers.wrappedBuffer(frame, data);
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(spdySynReplyFrame));
|
||||
byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length = (headerBlockLength == 0) ? 8 : 6 + headerBlockLength;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(SPDY_SYN_REPLY_FRAME);
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(length);
|
||||
frame.writeInt(spdySynReplyFrame.getStreamID());
|
||||
if (data.readableBytes() == 0) {
|
||||
frame.writeInt(0);
|
||||
} else {
|
||||
frame.writeShort(0);
|
||||
}
|
||||
return ChannelBuffers.wrappedBuffer(frame, data);
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(SPDY_RST_STREAM_FRAME);
|
||||
frame.writeInt(8);
|
||||
frame.writeInt(spdyRstStreamFrame.getStreamID());
|
||||
frame.writeInt(spdyRstStreamFrame.getStatus().getCode());
|
||||
return frame;
|
||||
|
||||
} else if (msg instanceof SpdySettingsFrame) {
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
byte flags = spdySettingsFrame.clearPreviouslyPersistedSettings() ?
|
||||
SPDY_SETTINGS_CLEAR : 0;
|
||||
Set<Integer> IDs = spdySettingsFrame.getIDs();
|
||||
int numEntries = IDs.size();
|
||||
int length = 4 + numEntries * 8;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(SPDY_SETTINGS_FRAME);
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(length);
|
||||
frame.writeInt(numEntries);
|
||||
for (Integer ID: IDs) {
|
||||
int id = ID.intValue();
|
||||
byte ID_flags = (byte) 0;
|
||||
if (spdySettingsFrame.persistValue(id)) {
|
||||
ID_flags |= SPDY_SETTINGS_PERSIST_VALUE;
|
||||
}
|
||||
if (spdySettingsFrame.isPersisted(id)) {
|
||||
ID_flags |= SPDY_SETTINGS_PERSISTED;
|
||||
}
|
||||
// Chromium Issue 79156
|
||||
// SPDY setting ids are not written in network byte order
|
||||
// Write id assuming the architecture is little endian
|
||||
frame.writeByte((id >> 0) & 0xFF);
|
||||
frame.writeByte((id >> 8) & 0xFF);
|
||||
frame.writeByte((id >> 16) & 0xFF);
|
||||
frame.writeByte(ID_flags);
|
||||
frame.writeInt(spdySettingsFrame.getValue(id));
|
||||
}
|
||||
return frame;
|
||||
|
||||
} else if (msg instanceof SpdyNoOpFrame) {
|
||||
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(SPDY_NOOP_FRAME);
|
||||
frame.writeInt(0);
|
||||
return frame;
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(SPDY_PING_FRAME);
|
||||
frame.writeInt(4);
|
||||
frame.writeInt(spdyPingFrame.getID());
|
||||
return frame;
|
||||
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 4);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(SPDY_GOAWAY_FRAME);
|
||||
frame.writeInt(4);
|
||||
frame.writeInt(spdyGoAwayFrame.getLastGoodStreamID());
|
||||
return frame;
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(spdyHeadersFrame));
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length = (headerBlockLength == 0) ? 4 : 6 + headerBlockLength;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
frame.writeShort(SPDY_HEADERS_FRAME);
|
||||
frame.writeInt(length);
|
||||
frame.writeInt(spdyHeadersFrame.getStreamID());
|
||||
if (data.readableBytes() != 0) {
|
||||
frame.writeShort(0);
|
||||
}
|
||||
return ChannelBuffers.wrappedBuffer(frame, data);
|
||||
}
|
||||
|
||||
// Unknown message type
|
||||
return msg;
|
||||
}
|
||||
|
||||
private ChannelBuffer encodeHeaderBlock(SpdyHeaderBlock headerFrame)
|
||||
throws Exception {
|
||||
Set<String> names = headerFrame.getHeaderNames();
|
||||
int numHeaders = names.size();
|
||||
if (numHeaders == 0) {
|
||||
return ChannelBuffers.EMPTY_BUFFER;
|
||||
}
|
||||
if (numHeaders > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"header block contains too many headers");
|
||||
}
|
||||
ChannelBuffer headerBlock = ChannelBuffers.dynamicBuffer(
|
||||
ByteOrder.BIG_ENDIAN, 256);
|
||||
headerBlock.writeShort(numHeaders);
|
||||
for (String name: names) {
|
||||
byte[] nameBytes = name.getBytes("UTF-8");
|
||||
headerBlock.writeShort(nameBytes.length);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
int savedIndex = headerBlock.writerIndex();
|
||||
int valueLength = 0;
|
||||
headerBlock.writeShort(valueLength);
|
||||
for (String value: headerFrame.getHeaders(name)) {
|
||||
byte[] valueBytes = value.getBytes("UTF-8");
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
valueLength += valueBytes.length + 1;
|
||||
}
|
||||
valueLength --;
|
||||
if (valueLength > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"header exceeds allowable length: " + name);
|
||||
}
|
||||
headerBlock.setShort(savedIndex, valueLength);
|
||||
headerBlock.writerIndex(headerBlock.writerIndex() - 1);
|
||||
}
|
||||
return headerBlock;
|
||||
}
|
||||
|
||||
private synchronized ChannelBuffer compressHeaderBlock(
|
||||
ChannelBuffer uncompressed) throws Exception {
|
||||
if (uncompressed.readableBytes() == 0) {
|
||||
return ChannelBuffers.EMPTY_BUFFER;
|
||||
}
|
||||
headerBlockCompressor.offer(uncompressed);
|
||||
return headerBlockCompressor.poll();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol GOAWAY Control Frame
|
||||
*/
|
||||
public interface SpdyGoAwayFrame {
|
||||
|
||||
/**
|
||||
* Returns the Last-good-stream-ID of this frame.
|
||||
*/
|
||||
int getLastGoodStreamID();
|
||||
|
||||
/**
|
||||
* Sets the Last-good-stream-ID of this frame. The Last-good-stream-ID
|
||||
* cannot be negative.
|
||||
*/
|
||||
void setLastGoodStreamID(int lastGoodStreamID);
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A SPDY Name/Value Header Block which provides common properties for
|
||||
* {@link SpdySynStreamFrame}, {@link SpdySynReplyFrame}, and
|
||||
* {@link SpdyHeadersFrame}.
|
||||
* @see SpdyHeaders
|
||||
*/
|
||||
public interface SpdyHeaderBlock {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void setInvalid();
|
||||
|
||||
/**
|
||||
* Returns the header value with the specified header name. If there is
|
||||
* more than one header value for the specified header name, the first
|
||||
* value is returned.
|
||||
*
|
||||
* @return the header value or {@code null} if there is no such header
|
||||
*/
|
||||
String getHeader(String name);
|
||||
|
||||
/**
|
||||
* Returns the header values with the specified header name.
|
||||
*
|
||||
* @return the {@link List} of header values. An empty list if there is no
|
||||
* such header.
|
||||
*/
|
||||
List<String> getHeaders(String name);
|
||||
|
||||
/**
|
||||
* Returns all header names and values that this block contains.
|
||||
*
|
||||
* @return the {@link List} of the header name-value pairs. An empty list
|
||||
* if there is no header in this message.
|
||||
*/
|
||||
List<Map.Entry<String, String>> getHeaders();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if there is a header with the specified
|
||||
* header name.
|
||||
*/
|
||||
boolean containsHeader(String name);
|
||||
|
||||
/**
|
||||
* Returns the {@link Set} of all header names that this block contains.
|
||||
*/
|
||||
Set<String> getHeaderNames();
|
||||
|
||||
/**
|
||||
* Adds a new header with the specified name and value.
|
||||
*/
|
||||
void addHeader(String name, Object value);
|
||||
|
||||
/**
|
||||
* Sets a new header with the specified name and value. If there is an
|
||||
* existing header with the same name, the existing header is removed.
|
||||
*/
|
||||
void setHeader(String name, Object value);
|
||||
|
||||
/**
|
||||
* Sets a new header with the specified name and values. If there is an
|
||||
* existing header with the same name, the existing header is removed.
|
||||
*/
|
||||
void setHeader(String name, Iterable<?> values);
|
||||
|
||||
/**
|
||||
* Removes the header with the specified name.
|
||||
*/
|
||||
void removeHeader(String name);
|
||||
|
||||
/**
|
||||
* Removes all headers from this block.
|
||||
*/
|
||||
void clearHeaders();
|
||||
}
|
@ -0,0 +1,530 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.jboss.netty.handler.codec.http.HttpMethod;
|
||||
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
|
||||
import org.jboss.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
/**
|
||||
* Provides the constants for the standard SPDY HTTP header names and commonly
|
||||
* used utility methods that access an {@link SpdyHeaderBlock}.
|
||||
* @apiviz.sterotype static
|
||||
*/
|
||||
public class SpdyHeaders {
|
||||
|
||||
/**
|
||||
* SPDY HTTP header names
|
||||
* @apiviz.sterotype static
|
||||
*/
|
||||
public static final class HttpNames {
|
||||
/**
|
||||
* {@code "method"}
|
||||
*/
|
||||
public static final String METHOD = "method";
|
||||
/**
|
||||
* {@code "scheme"}
|
||||
*/
|
||||
public static final String SCHEME = "scheme";
|
||||
/**
|
||||
* {@code "status"}
|
||||
*/
|
||||
public static final String STATUS = "status";
|
||||
/**
|
||||
* {@code "url"}
|
||||
*/
|
||||
public static final String URL = "url";
|
||||
/**
|
||||
* {@code "version"}
|
||||
*/
|
||||
public static final String VERSION = "version";
|
||||
|
||||
private HttpNames() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the header value with the specified header name. If there are
|
||||
* more than one header value for the specified header name, the first
|
||||
* value is returned.
|
||||
*
|
||||
* @return the header value or {@code null} if there is no such header
|
||||
*/
|
||||
public static String getHeader(SpdyHeaderBlock block, String name) {
|
||||
return block.getHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the header value with the specified header name. If there are
|
||||
* more than one header value for the specified header name, the first
|
||||
* value is returned.
|
||||
*
|
||||
* @return the header value or the {@code defaultValue} if there is no such
|
||||
* header
|
||||
*/
|
||||
public static String getHeader(SpdyHeaderBlock block, String name, String defaultValue) {
|
||||
String value = block.getHeader(name);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new header with the specified name and value. If there is an
|
||||
* existing header with the same name, the existing header is removed.
|
||||
*/
|
||||
public static void setHeader(SpdyHeaderBlock block, String name, Object value) {
|
||||
block.setHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new header with the specified name and values. If there is an
|
||||
* existing header with the same name, the existing header is removed.
|
||||
*/
|
||||
public static void setHeader(SpdyHeaderBlock block, String name, Iterable<?> values) {
|
||||
block.setHeader(name, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new header with the specified name and value.
|
||||
*/
|
||||
public static void addHeader(SpdyHeaderBlock block, String name, Object value) {
|
||||
block.addHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "method"} header.
|
||||
*/
|
||||
public static void removeMethod(SpdyHeaderBlock block) {
|
||||
block.removeHeader(HttpNames.METHOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpMethod} represented by the {@code "method"} header.
|
||||
*/
|
||||
public static HttpMethod getMethod(SpdyHeaderBlock block) {
|
||||
try {
|
||||
return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "method"} header.
|
||||
*/
|
||||
public static void setMethod(SpdyHeaderBlock block, HttpMethod method) {
|
||||
block.setHeader(HttpNames.METHOD, method.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "scheme"} header.
|
||||
*/
|
||||
public static void removeScheme(SpdyHeaderBlock block) {
|
||||
block.removeHeader(HttpNames.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "scheme"} header.
|
||||
*/
|
||||
public static String getScheme(SpdyHeaderBlock block) {
|
||||
return block.getHeader(HttpNames.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "scheme"} header.
|
||||
*/
|
||||
public static void setScheme(SpdyHeaderBlock block, String value) {
|
||||
block.setHeader(HttpNames.SCHEME, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "status"} header.
|
||||
*/
|
||||
public static void removeStatus(SpdyHeaderBlock block) {
|
||||
block.removeHeader(HttpNames.STATUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpResponseStatus} represented by the {@code "status"} header.
|
||||
*/
|
||||
public static HttpResponseStatus getStatus(SpdyHeaderBlock block) {
|
||||
try {
|
||||
String status = block.getHeader(HttpNames.STATUS);
|
||||
int space = status.indexOf(' ');
|
||||
if (space == -1) {
|
||||
return HttpResponseStatus.valueOf(Integer.parseInt(status));
|
||||
} else {
|
||||
int code = Integer.parseInt(status.substring(0, space));
|
||||
String reasonPhrase = status.substring(space + 1);
|
||||
HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
|
||||
if (responseStatus.getReasonPhrase().equals(responseStatus)) {
|
||||
return responseStatus;
|
||||
} else {
|
||||
return new HttpResponseStatus(code, reasonPhrase);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "status"} header.
|
||||
*/
|
||||
public static void setStatus(SpdyHeaderBlock block, HttpResponseStatus status) {
|
||||
block.setHeader(HttpNames.STATUS, status.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "url"} header.
|
||||
*/
|
||||
public static void removeUrl(SpdyHeaderBlock block) {
|
||||
block.removeHeader(HttpNames.URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "url"} header.
|
||||
*/
|
||||
public static String getUrl(SpdyHeaderBlock block) {
|
||||
return block.getHeader(HttpNames.URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "url"} header.
|
||||
*/
|
||||
public static void setUrl(SpdyHeaderBlock block, String value) {
|
||||
block.setHeader(HttpNames.URL, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "version"} header.
|
||||
*/
|
||||
public static void removeVersion(SpdyHeaderBlock block) {
|
||||
block.removeHeader(HttpNames.VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpVersion} represented by the {@code "version"} header.
|
||||
*/
|
||||
public static HttpVersion getVersion(SpdyHeaderBlock block) {
|
||||
try {
|
||||
return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "version"} header.
|
||||
*/
|
||||
public static void setVersion(SpdyHeaderBlock block, HttpVersion version) {
|
||||
block.setHeader(HttpNames.VERSION, version.getText());
|
||||
}
|
||||
|
||||
|
||||
private static final int BUCKET_SIZE = 17;
|
||||
|
||||
private static int hash(String name) {
|
||||
int h = 0;
|
||||
for (int i = name.length() - 1; i >= 0; i --) {
|
||||
char c = name.charAt(i);
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 32;
|
||||
}
|
||||
h = 31 * h + c;
|
||||
}
|
||||
|
||||
if (h > 0) {
|
||||
return h;
|
||||
} else if (h == Integer.MIN_VALUE) {
|
||||
return Integer.MAX_VALUE;
|
||||
} else {
|
||||
return -h;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean eq(String name1, String name2) {
|
||||
int nameLen = name1.length();
|
||||
if (nameLen != name2.length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = nameLen - 1; i >= 0; i --) {
|
||||
char c1 = name1.charAt(i);
|
||||
char c2 = name2.charAt(i);
|
||||
if (c1 != c2) {
|
||||
if (c1 >= 'A' && c1 <= 'Z') {
|
||||
c1 += 32;
|
||||
}
|
||||
if (c2 >= 'A' && c2 <= 'Z') {
|
||||
c2 += 32;
|
||||
}
|
||||
if (c1 != c2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int index(int hash) {
|
||||
return hash % BUCKET_SIZE;
|
||||
}
|
||||
|
||||
private final Entry[] entries = new Entry[BUCKET_SIZE];
|
||||
private final Entry head = new Entry(-1, null, null);
|
||||
|
||||
SpdyHeaders() {
|
||||
head.before = head.after = head;
|
||||
}
|
||||
|
||||
void addHeader(final String name, final Object value) {
|
||||
String lowerCaseName = name.toLowerCase();
|
||||
SpdyCodecUtil.validateHeaderName(lowerCaseName);
|
||||
String strVal = toString(value);
|
||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
||||
int h = hash(lowerCaseName);
|
||||
int i = index(h);
|
||||
addHeader0(h, i, lowerCaseName, strVal);
|
||||
}
|
||||
|
||||
private void addHeader0(int h, int i, final String name, final String value) {
|
||||
// Update the hash table.
|
||||
Entry e = entries[i];
|
||||
Entry newEntry;
|
||||
entries[i] = newEntry = new Entry(h, name, value);
|
||||
newEntry.next = e;
|
||||
|
||||
// Update the linked list.
|
||||
newEntry.addBefore(head);
|
||||
}
|
||||
|
||||
void removeHeader(final String name) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
String lowerCaseName = name.toLowerCase();
|
||||
int h = hash(lowerCaseName);
|
||||
int i = index(h);
|
||||
removeHeader0(h, i, lowerCaseName);
|
||||
}
|
||||
|
||||
private void removeHeader0(int h, int i, String name) {
|
||||
Entry e = entries[i];
|
||||
if (e == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (e.hash == h && eq(name, e.key)) {
|
||||
e.remove();
|
||||
Entry next = e.next;
|
||||
if (next != null) {
|
||||
entries[i] = next;
|
||||
e = next;
|
||||
} else {
|
||||
entries[i] = null;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
Entry next = e.next;
|
||||
if (next == null) {
|
||||
break;
|
||||
}
|
||||
if (next.hash == h && eq(name, next.key)) {
|
||||
e.next = next.next;
|
||||
next.remove();
|
||||
} else {
|
||||
e = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setHeader(final String name, final Object value) {
|
||||
String lowerCaseName = name.toLowerCase();
|
||||
SpdyCodecUtil.validateHeaderName(lowerCaseName);
|
||||
String strVal = toString(value);
|
||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
||||
int h = hash(lowerCaseName);
|
||||
int i = index(h);
|
||||
removeHeader0(h, i, lowerCaseName);
|
||||
addHeader0(h, i, lowerCaseName, strVal);
|
||||
}
|
||||
|
||||
void setHeader(final String name, final Iterable<?> values) {
|
||||
if (values == null) {
|
||||
throw new NullPointerException("values");
|
||||
}
|
||||
|
||||
String lowerCaseName = name.toLowerCase();
|
||||
SpdyCodecUtil.validateHeaderName(lowerCaseName);
|
||||
|
||||
int h = hash(lowerCaseName);
|
||||
int i = index(h);
|
||||
|
||||
removeHeader0(h, i, lowerCaseName);
|
||||
for (Object v: values) {
|
||||
if (v == null) {
|
||||
break;
|
||||
}
|
||||
String strVal = toString(v);
|
||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
||||
addHeader0(h, i, lowerCaseName, strVal);
|
||||
}
|
||||
}
|
||||
|
||||
void clearHeaders() {
|
||||
for (int i = 0; i < entries.length; i ++) {
|
||||
entries[i] = null;
|
||||
}
|
||||
head.before = head.after = head;
|
||||
}
|
||||
|
||||
String getHeader(final String name) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
|
||||
int h = hash(name);
|
||||
int i = index(h);
|
||||
Entry e = entries[i];
|
||||
while (e != null) {
|
||||
if (e.hash == h && eq(name, e.key)) {
|
||||
return e.value;
|
||||
}
|
||||
|
||||
e = e.next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> getHeaders(final String name) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
|
||||
LinkedList<String> values = new LinkedList<String>();
|
||||
|
||||
int h = hash(name);
|
||||
int i = index(h);
|
||||
Entry e = entries[i];
|
||||
while (e != null) {
|
||||
if (e.hash == h && eq(name, e.key)) {
|
||||
values.addFirst(e.value);
|
||||
}
|
||||
e = e.next;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
List<Map.Entry<String, String>> getHeaders() {
|
||||
List<Map.Entry<String, String>> all =
|
||||
new LinkedList<Map.Entry<String, String>>();
|
||||
|
||||
Entry e = head.after;
|
||||
while (e != head) {
|
||||
all.add(e);
|
||||
e = e.after;
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
boolean containsHeader(String name) {
|
||||
return getHeader(name) != null;
|
||||
}
|
||||
|
||||
Set<String> getHeaderNames() {
|
||||
Set<String> names = new TreeSet<String>();
|
||||
|
||||
Entry e = head.after;
|
||||
while (e != head) {
|
||||
names.add(e.key);
|
||||
e = e.after;
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private static String toString(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
private static final class Entry implements Map.Entry<String, String> {
|
||||
final int hash;
|
||||
final String key;
|
||||
String value;
|
||||
Entry next;
|
||||
Entry before, after;
|
||||
|
||||
Entry(int hash, String key, String value) {
|
||||
this.hash = hash;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
void remove() {
|
||||
before.after = after;
|
||||
after.before = before;
|
||||
}
|
||||
|
||||
void addBefore(Entry e) {
|
||||
after = e;
|
||||
before = e.before;
|
||||
before.after = this;
|
||||
after.before = this;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String setValue(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
SpdyCodecUtil.validateHeaderValue(value);
|
||||
String oldValue = this.value;
|
||||
this.value = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol HEADERS Control Frame
|
||||
*/
|
||||
public interface SpdyHeadersFrame extends SpdyHeaderBlock {
|
||||
|
||||
/**
|
||||
* Returns the Stream-ID of this frame.
|
||||
*/
|
||||
int getStreamID();
|
||||
|
||||
/**
|
||||
* Sets the Stream-ID of this frame. The Stream-ID must be positive.
|
||||
*/
|
||||
void setStreamID(int streamID);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol NOOP Control Frame
|
||||
*/
|
||||
public interface SpdyNoOpFrame {
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol PING Control Frame
|
||||
*/
|
||||
public interface SpdyPingFrame {
|
||||
|
||||
/**
|
||||
* Returns the ID of this frame.
|
||||
*/
|
||||
int getID();
|
||||
|
||||
/**
|
||||
* Sets the ID of this frame.
|
||||
*/
|
||||
void setID(int ID);
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* An {@link Exception} which is thrown when the received frame cannot
|
||||
* be decoded by the {@link SpdyFrameDecoder}.
|
||||
* @apiviz.exclude
|
||||
*/
|
||||
public class SpdyProtocolException extends Exception {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public SpdyProtocolException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public SpdyProtocolException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public SpdyProtocolException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public SpdyProtocolException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol RST_STREAM Control Frame
|
||||
*/
|
||||
public interface SpdyRstStreamFrame {
|
||||
|
||||
/**
|
||||
* Returns the Stream-ID of this frame.
|
||||
*/
|
||||
int getStreamID();
|
||||
|
||||
/**
|
||||
* Sets the Stream-ID of this frame. The Stream-ID must be positive.
|
||||
*/
|
||||
void setStreamID(int streamID);
|
||||
|
||||
/**
|
||||
* Returns the status of this frame.
|
||||
*/
|
||||
SpdyStreamStatus getStatus();
|
||||
|
||||
/**
|
||||
* Sets the status of this frame.
|
||||
*/
|
||||
void setStatus(SpdyStreamStatus status);
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
final class SpdySession {
|
||||
|
||||
private final Map<Integer, StreamState> activeStreams =
|
||||
new ConcurrentHashMap<Integer, StreamState>();
|
||||
|
||||
SpdySession() {
|
||||
}
|
||||
|
||||
public int numActiveStreams() {
|
||||
return activeStreams.size();
|
||||
}
|
||||
|
||||
public boolean noActiveStreams() {
|
||||
return activeStreams.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isActiveStream(int streamID) {
|
||||
return activeStreams.containsKey(new Integer(streamID));
|
||||
}
|
||||
|
||||
public void acceptStream(int streamID, boolean remoteSideClosed, boolean localSideClosed) {
|
||||
if (!remoteSideClosed || !localSideClosed) {
|
||||
activeStreams.put(new Integer(streamID),
|
||||
new StreamState(remoteSideClosed, localSideClosed));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public void removeStream(int streamID) {
|
||||
activeStreams.remove(new Integer(streamID));
|
||||
return;
|
||||
}
|
||||
|
||||
public boolean isRemoteSideClosed(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return (state == null) || state.isRemoteSideClosed();
|
||||
}
|
||||
|
||||
public void closeRemoteSide(int streamID) {
|
||||
Integer StreamID = new Integer(streamID);
|
||||
StreamState state = activeStreams.get(StreamID);
|
||||
if (state != null) {
|
||||
state.closeRemoteSide();
|
||||
if (state.isLocalSideClosed()) {
|
||||
activeStreams.remove(StreamID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLocalSideClosed(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return (state == null) || state.isLocalSideClosed();
|
||||
}
|
||||
|
||||
public void closeLocalSide(int streamID) {
|
||||
Integer StreamID = new Integer(streamID);
|
||||
StreamState state = activeStreams.get(StreamID);
|
||||
if (state != null) {
|
||||
state.closeLocalSide();
|
||||
if (state.isRemoteSideClosed()) {
|
||||
activeStreams.remove(StreamID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasReceivedReply(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
return (state != null) && state.hasReceivedReply();
|
||||
}
|
||||
|
||||
public void receivedReply(int streamID) {
|
||||
StreamState state = activeStreams.get(new Integer(streamID));
|
||||
if (state != null) {
|
||||
state.receivedReply();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamState {
|
||||
|
||||
private boolean remoteSideClosed;
|
||||
private boolean localSideClosed;
|
||||
private boolean receivedReply;
|
||||
|
||||
public StreamState(boolean remoteSideClosed, boolean localSideClosed) {
|
||||
this.remoteSideClosed = remoteSideClosed;
|
||||
this.localSideClosed = localSideClosed;
|
||||
}
|
||||
|
||||
public boolean isRemoteSideClosed() {
|
||||
return remoteSideClosed;
|
||||
}
|
||||
|
||||
public void closeRemoteSide() {
|
||||
remoteSideClosed = true;
|
||||
}
|
||||
|
||||
public boolean isLocalSideClosed() {
|
||||
return localSideClosed;
|
||||
}
|
||||
|
||||
public void closeLocalSide() {
|
||||
localSideClosed = true;
|
||||
}
|
||||
|
||||
public boolean hasReceivedReply() {
|
||||
return receivedReply;
|
||||
}
|
||||
|
||||
public void receivedReply() {
|
||||
receivedReply = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,471 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelDownstreamHandler;
|
||||
import org.jboss.netty.channel.ChannelEvent;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelFutureListener;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.channel.Channels;
|
||||
import org.jboss.netty.channel.MessageEvent;
|
||||
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||
|
||||
/**
|
||||
* Manages streams within a SPDY session.
|
||||
*/
|
||||
public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
implements ChannelDownstreamHandler {
|
||||
|
||||
private static final SpdyProtocolException PROTOCOL_EXCEPTION = new SpdyProtocolException();
|
||||
|
||||
private final SpdySession spdySession = new SpdySession();
|
||||
private volatile int lastGoodStreamID;
|
||||
|
||||
private volatile int remoteConcurrentStreams;
|
||||
private volatile int localConcurrentStreams;
|
||||
private volatile int maxConcurrentStreams;
|
||||
|
||||
private final AtomicInteger pings = new AtomicInteger();
|
||||
|
||||
private volatile boolean sentGoAwayFrame;
|
||||
private volatile boolean receivedGoAwayFrame;
|
||||
|
||||
private volatile ChannelFuture closeSessionFuture;
|
||||
|
||||
private final boolean server;
|
||||
|
||||
/**
|
||||
* Creates a new session handler.
|
||||
*
|
||||
* @param server {@code true} if and only if this session handler should
|
||||
* handle the server endpoint of the connection.
|
||||
* {@code false} if and only if this session handler should
|
||||
* handle the client endpoint of the connection.
|
||||
*/
|
||||
public SpdySessionHandler(boolean server) {
|
||||
super();
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
|
||||
Object msg = e.getMessage();
|
||||
if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
/*
|
||||
* SPDY Data frame processing requirements:
|
||||
*
|
||||
* If an endpoint receives a data frame for a Stream-ID which does not exist,
|
||||
* it must return a RST_STREAM with error code INVALID_STREAM for the Stream-ID.
|
||||
*
|
||||
* If an endpoint which created the stream receives a data frame before receiving
|
||||
* a SYN_REPLY on that stream, it is a protocol error, and the receiver should
|
||||
* close the connection immediately.
|
||||
*
|
||||
* If an endpoint receives multiple data frames for invalid Stream-IDs,
|
||||
* it may terminate the session.
|
||||
*
|
||||
* If an endpoint refuses a stream it must ignore any data frames for that stream.
|
||||
*
|
||||
* If an endpoint receives data on a stream which has already been torn down,
|
||||
* it must ignore the data received after the teardown.
|
||||
*/
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
int streamID = spdyDataFrame.getStreamID();
|
||||
|
||||
// Check if we received a data frame for a Stream-ID which is not open
|
||||
if (spdySession.isRemoteSideClosed(streamID)) {
|
||||
if (!sentGoAwayFrame) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we received a data frame before receiving a SYN_REPLY
|
||||
if (!isRemoteInitiatedID(streamID) && !spdySession.hasReceivedReply(streamID)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spdyDataFrame.isLast()) {
|
||||
// Close remote side of stream
|
||||
halfCloseStream(streamID, true);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
/*
|
||||
* SPDY SYN_STREAM frame processing requirements:
|
||||
*
|
||||
* If an endpoint receives a SYN_STREAM with a Stream-ID that is not monotonically
|
||||
* increasing, it must issue a session error with the status 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.
|
||||
*/
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
int streamID = spdySynStreamFrame.getStreamID();
|
||||
|
||||
// Check if we received a valid SYN_STREAM frame
|
||||
if (spdySynStreamFrame.isInvalid() ||
|
||||
!isRemoteInitiatedID(streamID) ||
|
||||
spdySession.isActiveStream(streamID)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stream-IDs must be monotonically increassing
|
||||
if (streamID < lastGoodStreamID) {
|
||||
issueSessionError(ctx, e.getChannel(), e.getRemoteAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to accept the stream
|
||||
boolean remoteSideClosed = spdySynStreamFrame.isLast();
|
||||
boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
|
||||
if (!acceptStream(streamID, remoteSideClosed, localSideClosed)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.REFUSED_STREAM);
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
/*
|
||||
* SPDY SYN_REPLY frame processing requirements:
|
||||
*
|
||||
* If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID
|
||||
* it must issue a stream error with the status code PROTOCOL_ERROR.
|
||||
*/
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
int streamID = spdySynReplyFrame.getStreamID();
|
||||
|
||||
// Check if we received a valid SYN_REPLY frame
|
||||
if (spdySynReplyFrame.isInvalid() ||
|
||||
isRemoteInitiatedID(streamID) ||
|
||||
spdySession.isRemoteSideClosed(streamID)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.INVALID_STREAM);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have received multiple frames for the same Stream-ID
|
||||
if (spdySession.hasReceivedReply(streamID)) {
|
||||
issueStreamError(ctx, e, streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
spdySession.receivedReply(streamID);
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
// Close remote side of stream
|
||||
halfCloseStream(streamID, true);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
/*
|
||||
* SPDY RST_STREAM frame processing requirements:
|
||||
*
|
||||
* After receiving a RST_STREAM on a stream, the receiver must not send additional
|
||||
* frames on that stream.
|
||||
*/
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
removeStream(spdyRstStreamFrame.getStreamID());
|
||||
|
||||
} else if (msg instanceof SpdySettingsFrame) {
|
||||
|
||||
/*
|
||||
* Only concerned with MAX_CONCURRENT_STREAMS
|
||||
*/
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
updateConcurrentStreams(spdySettingsFrame, true);
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
/*
|
||||
* SPDY 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
|
||||
*/
|
||||
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
|
||||
if (isRemoteInitiatedID(spdyPingFrame.getID())) {
|
||||
Channels.write(ctx, Channels.future(e.getChannel()), spdyPingFrame, e.getRemoteAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: only checks that there are outstanding pings since uniqueness is not inforced
|
||||
if (pings.get() == 0) {
|
||||
return;
|
||||
}
|
||||
pings.getAndDecrement();
|
||||
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
receivedGoAwayFrame = true;
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
if (spdyHeadersFrame.isInvalid()) {
|
||||
issueStreamError(ctx, e, spdyHeadersFrame.getStreamID(), SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.messageReceived(ctx, e);
|
||||
}
|
||||
|
||||
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt)
|
||||
throws Exception {
|
||||
if (evt instanceof ChannelStateEvent) {
|
||||
ChannelStateEvent e = (ChannelStateEvent) evt;
|
||||
switch (e.getState()) {
|
||||
case OPEN:
|
||||
case CONNECTED:
|
||||
case BOUND:
|
||||
if (Boolean.FALSE.equals(e.getValue()) || e.getValue() == null) {
|
||||
sendGoAwayFrame(ctx, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(evt instanceof MessageEvent)) {
|
||||
ctx.sendDownstream(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
MessageEvent e = (MessageEvent) evt;
|
||||
Object msg = e.getMessage();
|
||||
|
||||
if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
int streamID = spdyDataFrame.getStreamID();
|
||||
|
||||
if (spdySession.isLocalSideClosed(streamID)) {
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spdyDataFrame.isLast()) {
|
||||
halfCloseStream(streamID, false);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional();
|
||||
boolean localSideClosed = spdySynStreamFrame.isLast();
|
||||
if (!acceptStream(spdySynStreamFrame.getStreamID(), remoteSideClosed, localSideClosed)) {
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
int streamID = spdySynReplyFrame.getStreamID();
|
||||
|
||||
if (!isRemoteInitiatedID(streamID) || spdySession.isLocalSideClosed(streamID)) {
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
halfCloseStream(streamID, false);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
removeStream(spdyRstStreamFrame.getStreamID());
|
||||
|
||||
} else if (msg instanceof SpdySettingsFrame) {
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
updateConcurrentStreams(spdySettingsFrame, false);
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
if (isRemoteInitiatedID(spdyPingFrame.getID())) {
|
||||
e.getFuture().setFailure(new IllegalArgumentException(
|
||||
"invalid PING ID: " + spdyPingFrame.getID()));
|
||||
return;
|
||||
}
|
||||
pings.getAndIncrement();
|
||||
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
// Should send a CLOSE ChannelStateEvent
|
||||
e.getFuture().setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
ctx.sendDownstream(evt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Error Handling
|
||||
*/
|
||||
|
||||
private void issueSessionError(
|
||||
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) {
|
||||
|
||||
ChannelFuture future = sendGoAwayFrame(ctx, channel, remoteAddress);
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
// Send a RST_STREAM frame in response to an incoming MessageEvent
|
||||
// Only called in the upstream direction
|
||||
private void issueStreamError(
|
||||
ChannelHandlerContext ctx, MessageEvent e, int streamID, SpdyStreamStatus status) {
|
||||
|
||||
removeStream(streamID);
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamID, status);
|
||||
Channels.write(ctx, Channels.future(e.getChannel()), spdyRstStreamFrame, e.getRemoteAddress());
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
private boolean isServerID(int ID) {
|
||||
return ID % 2 == 0;
|
||||
}
|
||||
|
||||
private boolean isRemoteInitiatedID(int ID) {
|
||||
boolean serverID = isServerID(ID);
|
||||
return (server && !serverID) || (!server && serverID);
|
||||
}
|
||||
|
||||
private synchronized void updateConcurrentStreams(SpdySettingsFrame settings, boolean remote) {
|
||||
int newConcurrentStreams = settings.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
if (newConcurrentStreams > 0) {
|
||||
if (remote) {
|
||||
remoteConcurrentStreams = newConcurrentStreams;
|
||||
if ((localConcurrentStreams == 0) || localConcurrentStreams > remoteConcurrentStreams) {
|
||||
maxConcurrentStreams = remoteConcurrentStreams;
|
||||
}
|
||||
} else {
|
||||
localConcurrentStreams = newConcurrentStreams;
|
||||
if ((remoteConcurrentStreams == 0) || remoteConcurrentStreams > localConcurrentStreams) {
|
||||
maxConcurrentStreams = localConcurrentStreams;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// need to synchronize accesses to sentGoAwayFrame and lastGoodStreamID
|
||||
private synchronized boolean acceptStream(
|
||||
int streamID, boolean remoteSideClosed, boolean localSideClosed) {
|
||||
// Cannot initiate any new streams after receiving or sending GOAWAY
|
||||
if (receivedGoAwayFrame || sentGoAwayFrame) {
|
||||
return false;
|
||||
}
|
||||
if ((maxConcurrentStreams != 0) &&
|
||||
(spdySession.numActiveStreams() >= maxConcurrentStreams)) {
|
||||
return false;
|
||||
}
|
||||
spdySession.acceptStream(streamID, remoteSideClosed, localSideClosed);
|
||||
if (isRemoteInitiatedID(streamID)) {
|
||||
lastGoodStreamID = streamID;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void halfCloseStream(int streamID, boolean remote) {
|
||||
if (remote) {
|
||||
spdySession.closeRemoteSide(streamID);
|
||||
} else {
|
||||
spdySession.closeLocalSide(streamID);
|
||||
}
|
||||
if ((closeSessionFuture != null) && spdySession.noActiveStreams()) {
|
||||
closeSessionFuture.setSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeStream(int streamID) {
|
||||
spdySession.removeStream(streamID);
|
||||
if ((closeSessionFuture != null) && spdySession.noActiveStreams()) {
|
||||
closeSessionFuture.setSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelStateEvent e) {
|
||||
// Avoid NotYetConnectedException
|
||||
if (!e.getChannel().isConnected()) {
|
||||
ctx.sendDownstream(e);
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelFuture future = sendGoAwayFrame(ctx, e.getChannel(), null);
|
||||
if (spdySession.noActiveStreams()) {
|
||||
future.addListener(new ClosingChannelFutureListener(ctx, e));
|
||||
} else {
|
||||
closeSessionFuture = Channels.future(e.getChannel());
|
||||
closeSessionFuture.addListener(new ClosingChannelFutureListener(ctx, e));
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized ChannelFuture sendGoAwayFrame(
|
||||
ChannelHandlerContext ctx, Channel channel, SocketAddress remoteAddress) {
|
||||
if (!sentGoAwayFrame) {
|
||||
sentGoAwayFrame = true;
|
||||
ChannelFuture future = Channels.future(channel);
|
||||
Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID));
|
||||
return future;
|
||||
}
|
||||
return Channels.succeededFuture(channel);
|
||||
}
|
||||
|
||||
private static final class ClosingChannelFutureListener implements ChannelFutureListener {
|
||||
|
||||
private final ChannelHandlerContext ctx;
|
||||
private final ChannelStateEvent e;
|
||||
|
||||
ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelStateEvent e) {
|
||||
this.ctx = ctx;
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
public void operationComplete(ChannelFuture sentGoAwayFuture) throws Exception {
|
||||
if (!(sentGoAwayFuture.getCause() instanceof ClosedChannelException)) {
|
||||
Channels.close(ctx, e.getFuture());
|
||||
} else {
|
||||
e.getFuture().setSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol SETTINGS Control Frame
|
||||
*/
|
||||
public interface SpdySettingsFrame {
|
||||
|
||||
int SETTINGS_UPLOAD_BANDWIDTH = 1;
|
||||
int SETTINGS_DOWNLOAD_BANDWIDTH = 2;
|
||||
int SETTINGS_ROUND_TRIP_TIME = 3;
|
||||
int SETTINGS_MAX_CONCURRENT_STREAMS = 4;
|
||||
int SETTINGS_CURRENT_CWND = 5;
|
||||
int SETTINGS_DOWNLOAD_RETRANS_RATE = 6;
|
||||
int SETTINGS_INITIAL_WINDOW_SIZE = 7;
|
||||
|
||||
/**
|
||||
* 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 must be positive and cannot exceeed 16777215.
|
||||
*/
|
||||
void setValue(int ID, int value);
|
||||
|
||||
/**
|
||||
* Sets the value of the setting ID.
|
||||
* Sets if the setting should be persisted (should only be set by the server).
|
||||
* Sets if the setting is persisted (should only be set by the client).
|
||||
* The ID must be positive and cannot exceed 16777215.
|
||||
*/
|
||||
void setValue(int ID, int value, boolean persistVal, boolean persisted);
|
||||
|
||||
/**
|
||||
* Removes the value of the setting ID.
|
||||
* Removes all persistance information for the setting.
|
||||
*/
|
||||
void removeValue(int ID);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this setting should be persisted.
|
||||
* Returns {@code false} if this setting should not be persisted
|
||||
* or if the setting ID has no value.
|
||||
*/
|
||||
boolean persistValue(int ID);
|
||||
|
||||
/**
|
||||
* Sets if this setting should be persisted.
|
||||
* Has no effect if the setting ID has no value.
|
||||
*/
|
||||
void setPersistValue(int ID, boolean persistValue);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this setting is persisted.
|
||||
* Returns {@code false} if this setting should not be persisted
|
||||
* or if the setting ID has no value.
|
||||
*/
|
||||
boolean isPersisted(int ID);
|
||||
|
||||
/**
|
||||
* Sets if this setting is persisted.
|
||||
* Has no effect if the setting ID has no value.
|
||||
*/
|
||||
void setPersisted(int ID, boolean persisted);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if previously persisted settings should be cleared.
|
||||
*/
|
||||
boolean clearPreviouslyPersistedSettings();
|
||||
|
||||
/**
|
||||
* Sets if previously persisted settings should be cleared.
|
||||
*/
|
||||
void setClearPreviouslyPersistedSettings(boolean clear);
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* The SPDY stream status code and its description.
|
||||
* @apiviz.exclude
|
||||
*/
|
||||
public class SpdyStreamStatus implements Comparable<SpdyStreamStatus> {
|
||||
|
||||
/**
|
||||
* 1 Protocol Error
|
||||
*/
|
||||
public static final SpdyStreamStatus PROTOCOL_ERROR =
|
||||
new SpdyStreamStatus(1, "PROTOCOL_ERROR");
|
||||
|
||||
/**
|
||||
* 2 Invalid Stream
|
||||
*/
|
||||
public static final SpdyStreamStatus INVALID_STREAM =
|
||||
new SpdyStreamStatus(2, "INVALID_STREAM");
|
||||
|
||||
/**
|
||||
* 3 Refused Stream
|
||||
*/
|
||||
public static final SpdyStreamStatus REFUSED_STREAM =
|
||||
new SpdyStreamStatus(3, "REFUSED_STREAM");
|
||||
|
||||
/**
|
||||
* 4 Unsupported Version
|
||||
*/
|
||||
public static final SpdyStreamStatus UNSUPPORTED_VERSION =
|
||||
new SpdyStreamStatus(4, "UNSUPPORTED_VERSION");
|
||||
|
||||
/**
|
||||
* 5 Cancel
|
||||
*/
|
||||
public static final SpdyStreamStatus CANCEL =
|
||||
new SpdyStreamStatus(5, "CANCEL");
|
||||
|
||||
/**
|
||||
* 6 Internal Error
|
||||
*/
|
||||
public static final SpdyStreamStatus INTERNAL_ERROR =
|
||||
new SpdyStreamStatus(6, "INTERNAL_ERROR");
|
||||
|
||||
/**
|
||||
* 7 Flow Control Error
|
||||
*/
|
||||
public static final SpdyStreamStatus FLOW_CONTROL_ERROR =
|
||||
new SpdyStreamStatus(7, "FLOW_CONTROL_ERROR");
|
||||
|
||||
/**
|
||||
* Returns the {@link SpdyStreamStatus} represented by the specified code.
|
||||
* If the specified code is a defined SPDY status code, a cached instance
|
||||
* will be returned. Otherwise, a new instance will be returned.
|
||||
*/
|
||||
public static SpdyStreamStatus valueOf(int code) {
|
||||
if (code == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"0 is not a valid status code for a RST_STREAM");
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 1:
|
||||
return PROTOCOL_ERROR;
|
||||
case 2:
|
||||
return INVALID_STREAM;
|
||||
case 3:
|
||||
return REFUSED_STREAM;
|
||||
case 4:
|
||||
return UNSUPPORTED_VERSION;
|
||||
case 5:
|
||||
return CANCEL;
|
||||
case 6:
|
||||
return INTERNAL_ERROR;
|
||||
case 7:
|
||||
return FLOW_CONTROL_ERROR;
|
||||
}
|
||||
|
||||
return new SpdyStreamStatus(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 SpdyStreamStatus(int code, String statusPhrase) {
|
||||
if (code == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"0 is not a valid status code for a RST_STREAM");
|
||||
}
|
||||
|
||||
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 SpdyStreamStatus)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getCode() == ((SpdyStreamStatus) o).getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getStatusPhrase();
|
||||
}
|
||||
|
||||
public int compareTo(SpdyStreamStatus o) {
|
||||
return getCode() - o.getCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol SYN_REPLY Control Frame
|
||||
*/
|
||||
public interface SpdySynReplyFrame extends SpdyHeaderBlock {
|
||||
|
||||
/**
|
||||
* Returns the Stream-ID of this frame.
|
||||
*/
|
||||
int getStreamID();
|
||||
|
||||
/**
|
||||
* Sets the Stream-ID of this frame. The Stream-ID must be positive.
|
||||
*/
|
||||
void setStreamID(int streamID);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void setLast(boolean last);
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012 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 org.jboss.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol SYN_STREAM Control Frame
|
||||
*/
|
||||
public interface SpdySynStreamFrame extends SpdyHeaderBlock {
|
||||
|
||||
/**
|
||||
* Returns the Stream-ID of this frame.
|
||||
*/
|
||||
int getStreamID();
|
||||
|
||||
/**
|
||||
* Sets the Stream-ID of this frame. The Stream-ID must be positive.
|
||||
*/
|
||||
void setStreamID(int streamID);
|
||||
|
||||
/**
|
||||
* Returns the Associated-To-Stream-ID of this frame.
|
||||
*/
|
||||
int getAssociatedToStreamID();
|
||||
|
||||
/**
|
||||
* Sets the Associated-To-Stream-ID of this frame.
|
||||
* The Associated-To-Stream-ID cannot be negative.
|
||||
*/
|
||||
void setAssociatedToStreamID(int associatedToStreamID);
|
||||
|
||||
/**
|
||||
* Returns the priority of the stream.
|
||||
*/
|
||||
byte getPriority();
|
||||
|
||||
/**
|
||||
* Sets the priority of the stream.
|
||||
* The priority must be between 0 and 3 inclusive.
|
||||
*/
|
||||
void setPriority(byte priority);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void setLast(boolean last);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the stream created with this frame is to be
|
||||
* considered half-closed to the receiver.
|
||||
*/
|
||||
boolean isUnidirectional();
|
||||
|
||||
/**
|
||||
* Sets if the stream created with this frame is to be considered
|
||||
* half-closed to the receiver.
|
||||
*/
|
||||
void setUnidirectional(boolean unidirectional);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2012 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Encoder, decoder, session handler and their related message types for the SPDY protocol.
|
||||
*
|
||||
* @apiviz.exclude ^java\.lang\.
|
||||
* @apiviz.exclude OneToOne(Encoder|Decoder)$
|
||||
* @apiviz.exclude \.SpdyHeaders\.
|
||||
* @apiviz.exclude \.codec\.frame\.
|
||||
* @apiviz.exclude \.(Simple)?Channel[A-Za-z]*Handler$
|
||||
* @apiviz.exclude \.Default
|
||||
* @apiviz.exclude \.SpdyFrameCodec$
|
||||
*/
|
||||
package org.jboss.netty.handler.codec.spdy;
|
Loading…
Reference in New Issue
Block a user