Forward-porting SPDY codec
This commit is contained in:
parent
53f16ce003
commit
131eef2c51
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLast(boolean last) {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompressed() {
|
||||
return compressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCompressed(boolean compressed) {
|
||||
this.compressed = compressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelBuffer getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
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,94 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyGoAwayFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame {
|
||||
|
||||
private int lastGoodStreamID;
|
||||
private SpdySessionStatus status;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param lastGoodStreamID the Last-good-stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyGoAwayFrame(int lastGoodStreamID) {
|
||||
this(lastGoodStreamID, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param lastGoodStreamID the Last-good-stream-ID of this frame
|
||||
* @param statusCode the Status code of this frame
|
||||
*/
|
||||
public DefaultSpdyGoAwayFrame(int lastGoodStreamID, int statusCode) {
|
||||
this(lastGoodStreamID, SpdySessionStatus.valueOf(statusCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param lastGoodStreamID the Last-good-stream-ID of this frame
|
||||
* @param status the status of this frame
|
||||
*/
|
||||
public DefaultSpdyGoAwayFrame(int lastGoodStreamID, SpdySessionStatus status) {
|
||||
setLastGoodStreamID(lastGoodStreamID);
|
||||
setStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLastGoodStreamID() {
|
||||
return lastGoodStreamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastGoodStreamID(int lastGoodStreamID) {
|
||||
if (lastGoodStreamID < 0) {
|
||||
throw new IllegalArgumentException("Last-good-stream-ID"
|
||||
+ " cannot be negative: " + lastGoodStreamID);
|
||||
}
|
||||
this.lastGoodStreamID = lastGoodStreamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySessionStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(SpdySessionStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@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);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Status: ");
|
||||
buf.append(status.toString());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid() {
|
||||
return invalid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInvalid() {
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(final String name, final Object value) {
|
||||
headers.addHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(final String name, final Object value) {
|
||||
headers.setHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(final String name, final Iterable<?> values) {
|
||||
headers.setHeader(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHeader(final String name) {
|
||||
headers.removeHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHeaders() {
|
||||
headers.clearHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(final String name) {
|
||||
return headers.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(final String name) {
|
||||
return headers.getHeaders(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map.Entry<String, String>> getHeaders() {
|
||||
return headers.getHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsHeader(final String name) {
|
||||
return headers.containsHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
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,83 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyHeadersFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyHeadersFrame extends DefaultSpdyHeaderBlock
|
||||
implements SpdyHeadersFrame {
|
||||
|
||||
private int streamID;
|
||||
private boolean last;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamID the Stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyHeadersFrame(int streamID) {
|
||||
super();
|
||||
setStreamID(streamID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
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,33 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyNoOpFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyNoOpFrame implements SpdyNoOpFrame {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public DefaultSpdyNoOpFrame() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getID() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
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,85 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyStreamStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
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,200 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getIDs() {
|
||||
return settingsMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSet(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
return settingsMap.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
return settingsMap.get(key).getValue();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(int ID, int value) {
|
||||
setValue(ID, value, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
settingsMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persistValue(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
return settingsMap.get(key).getPersist();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPersistValue(int ID, boolean persistValue) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
settingsMap.get(key).setPersist(persistValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersisted(int ID) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
return settingsMap.get(key).getPersisted();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPersisted(int ID, boolean persisted) {
|
||||
Integer key = new Integer(ID);
|
||||
if (settingsMap.containsKey(key)) {
|
||||
settingsMap.get(key).setPersisted(persisted);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clearPreviouslyPersistedSettings() {
|
||||
return clear;
|
||||
}
|
||||
|
||||
@Override
|
||||
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,82 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
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,139 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAssociatedToStreamID() {
|
||||
return associatedToStreamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAssociatedToStreamID(int associatedToStreamID) {
|
||||
if (associatedToStreamID < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Associated-To-Stream-ID cannot be negative: " +
|
||||
associatedToStreamID);
|
||||
}
|
||||
this.associatedToStreamID = associatedToStreamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPriority(byte priority) {
|
||||
if (priority < 0 || priority > 7) {
|
||||
throw new IllegalArgumentException(
|
||||
"Priority must be between 0 and 7 inclusive: " + priority);
|
||||
}
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLast(boolean last) {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnidirectional() {
|
||||
return unidirectional;
|
||||
}
|
||||
|
||||
@Override
|
||||
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,80 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyWindowUpdateFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyWindowUpdateFrame implements SpdyWindowUpdateFrame {
|
||||
|
||||
private int streamID;
|
||||
private int deltaWindowSize;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamID the Stream-ID of this frame
|
||||
* @param deltaWindowSize the Delta-Window-Size of this frame
|
||||
*/
|
||||
public DefaultSpdyWindowUpdateFrame(int streamID, int deltaWindowSize) {
|
||||
setStreamID(streamID);
|
||||
setDeltaWindowSize(deltaWindowSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStreamID() {
|
||||
return streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamID(int streamID) {
|
||||
if (streamID <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream-ID must be positive: " + streamID);
|
||||
}
|
||||
this.streamID = streamID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeltaWindowSize() {
|
||||
return deltaWindowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeltaWindowSize(int deltaWindowSize) {
|
||||
if (deltaWindowSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Delta-Window-Size must be positive: " +
|
||||
deltaWindowSize);
|
||||
}
|
||||
this.deltaWindowSize = deltaWindowSize;
|
||||
}
|
||||
|
||||
@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("--> Delta-Window-Size = ");
|
||||
buf.append(deltaWindowSize);
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,366 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
final class SpdyCodecUtil {
|
||||
|
||||
static final int SPDY_MIN_VERSION = 2;
|
||||
static final int SPDY_MAX_VERSION = 3;
|
||||
|
||||
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 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 int SPDY_CREDENTIAL_FRAME = 10;
|
||||
|
||||
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
|
||||
static final byte[] SPDY_DICT = {
|
||||
0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i
|
||||
0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h
|
||||
0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p
|
||||
0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e
|
||||
0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - -
|
||||
0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - -
|
||||
0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t -
|
||||
0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
|
||||
0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e
|
||||
0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c
|
||||
0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - -
|
||||
0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l
|
||||
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e -
|
||||
0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
|
||||
0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s
|
||||
0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e -
|
||||
0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w
|
||||
0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o
|
||||
0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c
|
||||
0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r
|
||||
0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n
|
||||
0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e
|
||||
0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - -
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t -
|
||||
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e
|
||||
0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g
|
||||
0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o
|
||||
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - -
|
||||
0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
|
||||
0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - -
|
||||
0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t
|
||||
0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - -
|
||||
0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
|
||||
0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - -
|
||||
0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - -
|
||||
0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - -
|
||||
0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t
|
||||
0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i
|
||||
0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f
|
||||
0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i
|
||||
0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h -
|
||||
0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o
|
||||
0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s
|
||||
0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - -
|
||||
0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e -
|
||||
0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - -
|
||||
0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g
|
||||
0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f -
|
||||
0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i
|
||||
0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e
|
||||
0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t
|
||||
0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e
|
||||
0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - -
|
||||
0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r
|
||||
0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - -
|
||||
0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a -
|
||||
0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y
|
||||
0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - -
|
||||
0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - -
|
||||
0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - -
|
||||
0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r
|
||||
0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r
|
||||
0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r -
|
||||
0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e
|
||||
0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e -
|
||||
0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l
|
||||
0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r
|
||||
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e
|
||||
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g -
|
||||
0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a
|
||||
0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s
|
||||
0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t
|
||||
0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y
|
||||
0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a -
|
||||
0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i
|
||||
0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w
|
||||
0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n
|
||||
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - -
|
||||
0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d
|
||||
0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t -
|
||||
0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u
|
||||
0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0
|
||||
0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - -
|
||||
0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1
|
||||
0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r
|
||||
0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b
|
||||
0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s
|
||||
0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i
|
||||
0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e
|
||||
0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e -
|
||||
0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i
|
||||
0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2
|
||||
0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5
|
||||
0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0
|
||||
0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3
|
||||
0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7
|
||||
0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0
|
||||
0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4
|
||||
0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1
|
||||
0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1
|
||||
0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4
|
||||
0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4
|
||||
0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N
|
||||
0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o
|
||||
0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e
|
||||
0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 -
|
||||
0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e
|
||||
0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o
|
||||
0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m
|
||||
0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4
|
||||
0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0
|
||||
0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0
|
||||
0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d
|
||||
0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N
|
||||
0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d
|
||||
0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r
|
||||
0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o
|
||||
0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t
|
||||
0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e
|
||||
0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 -
|
||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e -
|
||||
0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a
|
||||
0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F
|
||||
0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A
|
||||
0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J
|
||||
0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A
|
||||
0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t -
|
||||
0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v -
|
||||
0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0
|
||||
0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n
|
||||
0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W
|
||||
0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u -
|
||||
0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a
|
||||
0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - -
|
||||
0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k
|
||||
0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t -
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a
|
||||
0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i
|
||||
0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g
|
||||
0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g
|
||||
0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
|
||||
0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l
|
||||
0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l
|
||||
0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t
|
||||
0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r
|
||||
0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l
|
||||
0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t
|
||||
0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e
|
||||
0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e
|
||||
0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d
|
||||
0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e
|
||||
0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c
|
||||
0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i
|
||||
0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 -
|
||||
0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - -
|
||||
0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 -
|
||||
};
|
||||
|
||||
private static final String SPDY2_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[] SPDY2_DICT;
|
||||
static {
|
||||
byte[] SPDY2_DICT_ = null;
|
||||
|
||||
try {
|
||||
SPDY2_DICT_ = SPDY2_DICT_S.getBytes("US-ASCII");
|
||||
// dictionary is null terminated
|
||||
SPDY2_DICT_[SPDY2_DICT_.length - 1] = (byte) 0;
|
||||
} catch (Exception e) {
|
||||
SPDY2_DICT_ = new byte[1];
|
||||
}
|
||||
|
||||
SPDY2_DICT = SPDY2_DICT_;
|
||||
}
|
||||
|
||||
|
||||
private SpdyCodecUtil() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a big-endian unsigned short integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedShort(ChannelBuffer buf, int offset) {
|
||||
return (buf.getByte(offset) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 1) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian unsigned medium integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedMedium(ChannelBuffer buf, int offset) {
|
||||
return (buf.getByte(offset) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 2) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian (31-bit) integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedInt(ChannelBuffer buf, int offset) {
|
||||
return (buf.getByte(offset) & 0x7F) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 3) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian signed integer from the buffer.
|
||||
*/
|
||||
static int getSignedInt(ChannelBuffer buf, int offset) {
|
||||
return (buf.getByte(offset) & 0xFF) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 3) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if ID is for a server initiated stream or ping.
|
||||
*/
|
||||
static boolean isServerID(int ID) {
|
||||
// Server initiated streams and pings have even IDs
|
||||
return ID % 2 == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,71 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.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);
|
||||
|
||||
/**
|
||||
* @deprecated Removed from SPDY specification.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isCompressed();
|
||||
|
||||
/**
|
||||
* @deprecated Removed from SPDY specification.
|
||||
*/
|
||||
@Deprecated
|
||||
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,49 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.channel.CombinedChannelHandler;
|
||||
|
||||
/**
|
||||
* A combination of {@link SpdyFrameDecoder} and {@link SpdyFrameEncoder}.
|
||||
*
|
||||
* @apiviz.has io.netty.handler.codec.spdy.SpdyFrameDecoder
|
||||
* @apiviz.has io.netty.handler.codec.spdy.SpdyFrameEncoder
|
||||
*/
|
||||
public class SpdyFrameCodec extends CombinedChannelHandler {
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version} and
|
||||
* the default decoder and encoder options
|
||||
* ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)},
|
||||
* {@code compressionLevel (6)}, {@code windowBits (15)},
|
||||
* and {@code memLevel (8)}).
|
||||
*/
|
||||
public SpdyFrameCodec(int version) {
|
||||
this(version, 8192, 16384, 6, 15, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified decoder and encoder options.
|
||||
*/
|
||||
public SpdyFrameCodec(
|
||||
int version, int maxChunkSize, int maxHeaderSize,
|
||||
int compressionLevel, int windowBits, int memLevel) {
|
||||
super(
|
||||
new SpdyFrameDecoder(version, maxChunkSize, maxHeaderSize),
|
||||
new SpdyFrameEncoder(version, compressionLevel, windowBits, memLevel));
|
||||
}
|
||||
}
|
@ -0,0 +1,790 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerContext;
|
||||
import io.netty.handler.codec.StreamToMessageDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
|
||||
/**
|
||||
* Decodes {@link ChannelBuffer}s into SPDY Data and Control Frames.
|
||||
*/
|
||||
public class SpdyFrameDecoder extends StreamToMessageDecoder<Object> {
|
||||
|
||||
private final int spdyVersion;
|
||||
private final int maxChunkSize;
|
||||
private final int maxHeaderSize;
|
||||
|
||||
private final SpdyHeaderBlockDecompressor headerBlockDecompressor;
|
||||
|
||||
private State state;
|
||||
private SpdySettingsFrame spdySettingsFrame;
|
||||
private SpdyHeaderBlock spdyHeaderBlock;
|
||||
|
||||
// SPDY common header fields
|
||||
private byte flags;
|
||||
private int length;
|
||||
private int version;
|
||||
private int type;
|
||||
private int streamID;
|
||||
|
||||
// Header block decoding fields
|
||||
private int headerSize;
|
||||
private int numHeaders;
|
||||
private ChannelBuffer decompressed;
|
||||
|
||||
private static enum State {
|
||||
READ_COMMON_HEADER,
|
||||
READ_CONTROL_FRAME,
|
||||
READ_SETTINGS_FRAME,
|
||||
READ_HEADER_BLOCK_FRAME,
|
||||
READ_HEADER_BLOCK,
|
||||
READ_DATA_FRAME,
|
||||
DISCARD_FRAME,
|
||||
FRAME_ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the default {@code version (2)},
|
||||
* {@code maxChunkSize (8192)}, and {@code maxHeaderSize (16384)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public SpdyFrameDecoder() {
|
||||
this(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version} and the default
|
||||
* {@code maxChunkSize (8192)} and {@code maxHeaderSize (16384)}.
|
||||
*/
|
||||
public SpdyFrameDecoder(int version) {
|
||||
this(version, 8192, 16384);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public SpdyFrameDecoder(int version, int maxChunkSize, int maxHeaderSize) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
if (maxChunkSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxChunkSize must be a positive integer: " + maxChunkSize);
|
||||
}
|
||||
if (maxHeaderSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxHeaderSize must be a positive integer: " + maxHeaderSize);
|
||||
}
|
||||
spdyVersion = version;
|
||||
this.maxChunkSize = maxChunkSize;
|
||||
this.maxHeaderSize = maxHeaderSize;
|
||||
headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(version);
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decodeLast(ChannelInboundHandlerContext<Byte> ctx, ChannelBuffer in) throws Exception {
|
||||
try {
|
||||
return decode(ctx, in);
|
||||
} finally {
|
||||
headerBlockDecompressor.end();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(ChannelInboundHandlerContext<Byte> ctx, ChannelBuffer buffer) throws Exception {
|
||||
switch(state) {
|
||||
case READ_COMMON_HEADER:
|
||||
state = readCommonHeader(buffer);
|
||||
if (state == State.FRAME_ERROR) {
|
||||
if (version != spdyVersion) {
|
||||
fireProtocolException(ctx, "Unsupported version: " + version);
|
||||
} else {
|
||||
fireInvalidControlFrameException(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// FrameDecoders must consume data when producing frames
|
||||
// All length 0 frames must be generated now
|
||||
if (length == 0) {
|
||||
if (state == State.READ_DATA_FRAME) {
|
||||
if (streamID == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireProtocolException(ctx, "Received invalid data frame");
|
||||
return null;
|
||||
}
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
|
||||
state = State.READ_COMMON_HEADER;
|
||||
return spdyDataFrame;
|
||||
}
|
||||
// There are no length 0 control frames
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case READ_CONTROL_FRAME:
|
||||
try {
|
||||
Object frame = readControlFrame(buffer);
|
||||
if (frame != null) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return frame;
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
}
|
||||
return null;
|
||||
|
||||
case READ_SETTINGS_FRAME:
|
||||
if (spdySettingsFrame == null) {
|
||||
// Validate frame length against number of entries
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return null;
|
||||
}
|
||||
int numEntries = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
// Each ID/Value entry is 8 bytes
|
||||
if ((length & 0x07) != 0 || length >> 3 != numEntries) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
return null;
|
||||
}
|
||||
|
||||
spdySettingsFrame = new DefaultSpdySettingsFrame();
|
||||
|
||||
boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0;
|
||||
spdySettingsFrame.setClearPreviouslyPersistedSettings(clear);
|
||||
}
|
||||
|
||||
int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
|
||||
for (int i = 0; i < readableEntries; i ++) {
|
||||
int ID;
|
||||
byte ID_flags;
|
||||
if (version < 3) {
|
||||
// Chromium Issue 79156
|
||||
// SPDY setting ids are not written in network byte order
|
||||
// Read id assuming the architecture is little endian
|
||||
ID = buffer.readByte() & 0xFF |
|
||||
(buffer.readByte() & 0xFF) << 8 |
|
||||
(buffer.readByte() & 0xFF) << 16;
|
||||
ID_flags = buffer.readByte();
|
||||
} else {
|
||||
ID_flags = buffer.readByte();
|
||||
ID = getUnsignedMedium(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(3);
|
||||
}
|
||||
int value = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
// Check for invalid ID -- avoid IllegalArgumentException in setValue
|
||||
if (ID == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
spdySettingsFrame = null;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
length -= 8 * readableEntries;
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
Object frame = spdySettingsFrame;
|
||||
spdySettingsFrame = null;
|
||||
return frame;
|
||||
}
|
||||
return null;
|
||||
|
||||
case READ_HEADER_BLOCK_FRAME:
|
||||
try {
|
||||
spdyHeaderBlock = readHeaderBlockFrame(buffer);
|
||||
if (spdyHeaderBlock != null) {
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
Object frame = spdyHeaderBlock;
|
||||
spdyHeaderBlock = null;
|
||||
return frame;
|
||||
}
|
||||
state = State.READ_HEADER_BLOCK;
|
||||
}
|
||||
return null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
return null;
|
||||
}
|
||||
|
||||
case READ_HEADER_BLOCK:
|
||||
int compressedBytes = Math.min(buffer.readableBytes(), length);
|
||||
length -= compressedBytes;
|
||||
|
||||
try {
|
||||
decodeHeaderBlock(buffer.readSlice(compressedBytes));
|
||||
} catch (Exception e) {
|
||||
state = State.FRAME_ERROR;
|
||||
spdyHeaderBlock = null;
|
||||
decompressed = null;
|
||||
ctx.fireExceptionCaught(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (spdyHeaderBlock != null && spdyHeaderBlock.isInvalid()) {
|
||||
Object frame = spdyHeaderBlock;
|
||||
spdyHeaderBlock = null;
|
||||
decompressed = null;
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
Object frame = spdyHeaderBlock;
|
||||
spdyHeaderBlock = null;
|
||||
state = State.READ_COMMON_HEADER;
|
||||
return frame;
|
||||
}
|
||||
return null;
|
||||
|
||||
case READ_DATA_FRAME:
|
||||
if (streamID == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireProtocolException(ctx, "Received invalid data frame");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate data frames that do not exceed maxChunkSize
|
||||
int dataLength = Math.min(maxChunkSize, length);
|
||||
|
||||
// Wait until entire frame is readable
|
||||
if (buffer.readableBytes() < dataLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
spdyDataFrame.setData(buffer.readBytes(dataLength));
|
||||
length -= dataLength;
|
||||
|
||||
if (length == 0) {
|
||||
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return spdyDataFrame;
|
||||
|
||||
case DISCARD_FRAME:
|
||||
int numBytes = Math.min(buffer.readableBytes(), length);
|
||||
buffer.skipBytes(numBytes);
|
||||
length -= numBytes;
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return null;
|
||||
|
||||
case FRAME_ERROR:
|
||||
buffer.skipBytes(buffer.readableBytes());
|
||||
return null;
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
}
|
||||
}
|
||||
|
||||
private State readCommonHeader(ChannelBuffer buffer) {
|
||||
// Wait until entire header is readable
|
||||
if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
|
||||
return State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
int frameOffset = buffer.readerIndex();
|
||||
int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
|
||||
int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
|
||||
buffer.skipBytes(SPDY_HEADER_SIZE);
|
||||
|
||||
// Read common header fields
|
||||
boolean control = (buffer.getByte(frameOffset) & 0x80) != 0;
|
||||
flags = buffer.getByte(flagsOffset);
|
||||
length = getUnsignedMedium(buffer, lengthOffset);
|
||||
|
||||
if (control) {
|
||||
// Decode control frame common header
|
||||
version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
|
||||
|
||||
int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET;
|
||||
type = getUnsignedShort(buffer, typeOffset);
|
||||
|
||||
// Check version first then validity
|
||||
if (version != spdyVersion || !isValidControlFrameHeader()) {
|
||||
return State.FRAME_ERROR;
|
||||
}
|
||||
|
||||
// Make sure decoder will produce a frame or consume input
|
||||
State nextState;
|
||||
if (willGenerateControlFrame()) {
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
case SPDY_HEADERS_FRAME:
|
||||
nextState = State.READ_HEADER_BLOCK_FRAME;
|
||||
break;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
nextState = State.READ_SETTINGS_FRAME;
|
||||
break;
|
||||
|
||||
default:
|
||||
nextState = State.READ_CONTROL_FRAME;
|
||||
}
|
||||
} else if (length != 0) {
|
||||
nextState = State.DISCARD_FRAME;
|
||||
} else {
|
||||
nextState = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return nextState;
|
||||
} else {
|
||||
// Decode data frame common header
|
||||
streamID = getUnsignedInt(buffer, frameOffset);
|
||||
|
||||
return State.READ_DATA_FRAME;
|
||||
}
|
||||
}
|
||||
|
||||
private Object readControlFrame(ChannelBuffer buffer) {
|
||||
int streamID;
|
||||
int statusCode;
|
||||
switch (type) {
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
return new DefaultSpdyRstStreamFrame(streamID, statusCode);
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int ID = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
return new DefaultSpdyPingFrame(ID);
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
int minLength = version < 3 ? 4 : 8;
|
||||
if (buffer.readableBytes() < minLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
if (version < 3) {
|
||||
return new DefaultSpdyGoAwayFrame(lastGoodStreamID);
|
||||
}
|
||||
|
||||
statusCode = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
return new DefaultSpdyGoAwayFrame(lastGoodStreamID, statusCode);
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
int deltaWindowSize = getUnsignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
return new DefaultSpdyWindowUpdateFrame(streamID, deltaWindowSize);
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
}
|
||||
}
|
||||
|
||||
private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) {
|
||||
int minLength;
|
||||
int streamID;
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
minLength = version < 3 ? 12 : 10;
|
||||
if (buffer.readableBytes() < minLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int offset = buffer.readerIndex();
|
||||
streamID = getUnsignedInt(buffer, offset);
|
||||
int associatedToStreamID = getUnsignedInt(buffer, offset + 4);
|
||||
byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
|
||||
if (version < 3) {
|
||||
priority >>= 1;
|
||||
}
|
||||
buffer.skipBytes(10);
|
||||
length -= 10;
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
buffer.skipBytes(2);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
|
||||
spdySynStreamFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
spdySynStreamFrame.setUnidirectional((flags & SPDY_FLAG_UNIDIRECTIONAL) != 0);
|
||||
|
||||
return spdySynStreamFrame;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
minLength = version < 3 ? 8 : 4;
|
||||
if (buffer.readableBytes() < minLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
// SPDY/2 has 16-bits of unused space
|
||||
if (version < 3) {
|
||||
buffer.skipBytes(2);
|
||||
length -= 2;
|
||||
}
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
buffer.skipBytes(2);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||
spdySynReplyFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
|
||||
return spdySynReplyFrame;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// SPDY/2 allows length 4 frame when there are no name/value pairs
|
||||
if (version < 3 && length > 4 && buffer.readableBytes() < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
// SPDY/2 has 16-bits of unused space
|
||||
if (version < 3 && length != 0) {
|
||||
buffer.skipBytes(2);
|
||||
length -= 2;
|
||||
}
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (version < 3 && length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
buffer.skipBytes(2);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
|
||||
spdyHeadersFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
|
||||
return spdyHeadersFrame;
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureBytes(int bytes) throws Exception {
|
||||
if (decompressed.readableBytes() >= bytes) {
|
||||
return true;
|
||||
}
|
||||
// Perhaps last call to decode filled output buffer
|
||||
headerBlockDecompressor.decode(decompressed);
|
||||
return decompressed.readableBytes() >= bytes;
|
||||
}
|
||||
|
||||
private int readLengthField() {
|
||||
if (version < 3) {
|
||||
return decompressed.readUnsignedShort();
|
||||
} else {
|
||||
return decompressed.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception {
|
||||
if (decompressed == null) {
|
||||
// First time we start to decode a header block
|
||||
// Initialize header block decoding fields
|
||||
headerSize = 0;
|
||||
numHeaders = -1;
|
||||
decompressed = ChannelBuffers.dynamicBuffer(8192);
|
||||
}
|
||||
|
||||
// Accumulate decompressed data
|
||||
headerBlockDecompressor.setInput(buffer);
|
||||
headerBlockDecompressor.decode(decompressed);
|
||||
|
||||
if (spdyHeaderBlock == null) {
|
||||
// Only decompressing data to keep decompression context in sync
|
||||
decompressed = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int lengthFieldSize = version < 3 ? 2 : 4;
|
||||
|
||||
if (numHeaders == -1) {
|
||||
// Read number of Name/Value pairs
|
||||
if (decompressed.readableBytes() < lengthFieldSize) {
|
||||
return;
|
||||
}
|
||||
numHeaders = readLengthField();
|
||||
if (numHeaders < 0) {
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (numHeaders > 0) {
|
||||
int headerSize = this.headerSize;
|
||||
decompressed.markReaderIndex();
|
||||
|
||||
// Try to read length of name
|
||||
if (!ensureBytes(lengthFieldSize)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
int nameLength = readLengthField();
|
||||
|
||||
// Recipients of a zero-length name must issue a stream error
|
||||
if (nameLength <= 0) {
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
headerSize += nameLength;
|
||||
if (headerSize > maxHeaderSize) {
|
||||
throw new TooLongFrameException(
|
||||
"Header block exceeds " + maxHeaderSize);
|
||||
}
|
||||
|
||||
// Try to read name
|
||||
if (!ensureBytes(nameLength)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
decompressed.readBytes(nameBytes);
|
||||
String name = new String(nameBytes, "UTF-8");
|
||||
|
||||
// Check for identically named headers
|
||||
if (spdyHeaderBlock.containsHeader(name)) {
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to read length of value
|
||||
if (!ensureBytes(lengthFieldSize)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
int valueLength = readLengthField();
|
||||
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
if (valueLength <= 0) {
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
headerSize += valueLength;
|
||||
if (headerSize > maxHeaderSize) {
|
||||
throw new TooLongFrameException(
|
||||
"Header block exceeds " + maxHeaderSize);
|
||||
}
|
||||
|
||||
// Try to read value
|
||||
if (!ensureBytes(valueLength)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
byte[] valueBytes = new byte[valueLength];
|
||||
decompressed.readBytes(valueBytes);
|
||||
|
||||
// Add Name/Value pair to headers
|
||||
int index = 0;
|
||||
int offset = 0;
|
||||
while (index < valueLength) {
|
||||
while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
|
||||
index ++;
|
||||
}
|
||||
if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
|
||||
// Received multiple, in-sequence NULL characters
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
String value = new String(valueBytes, offset, index - offset, "UTF-8");
|
||||
|
||||
try {
|
||||
spdyHeaderBlock.addHeader(name, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Name contains NULL or non-ascii characters
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
index ++;
|
||||
offset = index;
|
||||
}
|
||||
numHeaders --;
|
||||
this.headerSize = headerSize;
|
||||
}
|
||||
decompressed = null;
|
||||
}
|
||||
|
||||
private boolean isValidControlFrameHeader() {
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
return version < 3 ? length >= 12 : length >= 10;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
return version < 3 ? length >= 8 : length >= 4;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
return flags == 0 && length == 8;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
return length >= 4;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
return length == 0;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
return length == 4;
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
return version < 3 ? length == 4 : length == 8;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
if (version < 3) {
|
||||
return length == 4 || length >= 8;
|
||||
} else {
|
||||
return length >= 4;
|
||||
}
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return length == 8;
|
||||
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean willGenerateControlFrame() {
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
case SPDY_PING_FRAME:
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
case SPDY_HEADERS_FRAME:
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return true;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void fireInvalidControlFrameException(ChannelHandlerContext ctx) {
|
||||
String message = "Received invalid control frame";
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
message = "Received invalid SYN_STREAM control frame";
|
||||
break;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
message = "Received invalid SYN_REPLY control frame";
|
||||
break;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
message = "Received invalid RST_STREAM control frame";
|
||||
break;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
message = "Received invalid SETTINGS control frame";
|
||||
break;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
message = "Received invalid NOOP control frame";
|
||||
break;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
message = "Received invalid PING control frame";
|
||||
break;
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
message = "Received invalid GOAWAY control frame";
|
||||
break;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
message = "Received invalid HEADERS control frame";
|
||||
break;
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
message = "Received invalid WINDOW_UPDATE control frame";
|
||||
break;
|
||||
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
message = "Received invalid CREDENTIAL control frame";
|
||||
break;
|
||||
}
|
||||
fireProtocolException(ctx, message);
|
||||
}
|
||||
|
||||
private static void fireProtocolException(ChannelHandlerContext ctx, String message) {
|
||||
ctx.fireExceptionCaught(new SpdyProtocolException(message));
|
||||
}
|
||||
}
|
@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundHandlerContext;
|
||||
import io.netty.handler.codec.MessageToStreamEncoder;
|
||||
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Encodes a SPDY Data or Control Frame into a {@link ChannelBuffer}.
|
||||
*/
|
||||
public class SpdyFrameEncoder extends MessageToStreamEncoder<Object> {
|
||||
|
||||
private final int version;
|
||||
private volatile boolean finished;
|
||||
private final SpdyHeaderBlockCompressor headerBlockCompressor;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version} and the
|
||||
* default {@code compressionLevel (6)}, {@code windowBits (15)},
|
||||
* and {@code memLevel (8)}.
|
||||
*/
|
||||
public SpdyFrameEncoder(int version) {
|
||||
this(version, 6, 15, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public SpdyFrameEncoder(int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
super();
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unknown version: " + version);
|
||||
}
|
||||
this.version = version;
|
||||
headerBlockCompressor = SpdyHeaderBlockCompressor.newInstance(
|
||||
version, compressionLevel, windowBits, memLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
synchronized (headerBlockCompressor) {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
headerBlockCompressor.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncodable(Object msg) throws Exception {
|
||||
// FIXME: Introduce supertype
|
||||
return msg instanceof SpdyDataFrame ||
|
||||
msg instanceof SpdySynStreamFrame ||
|
||||
msg instanceof SpdySynReplyFrame ||
|
||||
msg instanceof SpdyRstStreamFrame ||
|
||||
msg instanceof SpdySettingsFrame ||
|
||||
msg instanceof SpdyNoOpFrame ||
|
||||
msg instanceof SpdyPingFrame ||
|
||||
msg instanceof SpdyGoAwayFrame ||
|
||||
msg instanceof SpdyHeadersFrame ||
|
||||
msg instanceof SpdyWindowUpdateFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ChannelOutboundHandlerContext<Object> ctx, Object msg, ChannelBuffer out) throws Exception {
|
||||
if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
ChannelBuffer data = spdyDataFrame.getData();
|
||||
byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0;
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + data.readableBytes());
|
||||
out.writeInt(spdyDataFrame.getStreamID() & 0x7FFFFFFF);
|
||||
out.writeByte(flags);
|
||||
out.writeMedium(data.readableBytes());
|
||||
out.writeBytes(data, data.readerIndex(), data.readableBytes());
|
||||
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(version, spdySynStreamFrame));
|
||||
byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
if (spdySynStreamFrame.isUnidirectional()) {
|
||||
flags |= SPDY_FLAG_UNIDIRECTIONAL;
|
||||
}
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length;
|
||||
if (version < 3) {
|
||||
length = headerBlockLength == 0 ? 12 : 10 + headerBlockLength;
|
||||
} else {
|
||||
length = 10 + headerBlockLength;
|
||||
}
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + length);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_SYN_STREAM_FRAME);
|
||||
out.writeByte(flags);
|
||||
out.writeMedium(length);
|
||||
out.writeInt(spdySynStreamFrame.getStreamID());
|
||||
out.writeInt(spdySynStreamFrame.getAssociatedToStreamID());
|
||||
if (version < 3) {
|
||||
// Restrict priorities for SPDY/2 to between 0 and 3
|
||||
byte priority = spdySynStreamFrame.getPriority();
|
||||
if (priority > 3) {
|
||||
priority = 3;
|
||||
}
|
||||
out.writeShort((priority & 0xFF) << 14);
|
||||
} else {
|
||||
out.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 13);
|
||||
}
|
||||
if (version < 3 && data.readableBytes() == 0) {
|
||||
out.writeShort(0);
|
||||
}
|
||||
out.writeBytes(data, data.readerIndex(), headerBlockLength);
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(version, spdySynReplyFrame));
|
||||
byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length;
|
||||
if (version < 3) {
|
||||
length = headerBlockLength == 0 ? 8 : 6 + headerBlockLength;
|
||||
} else {
|
||||
length = 4 + headerBlockLength;
|
||||
}
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + length);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_SYN_REPLY_FRAME);
|
||||
out.writeByte(flags);
|
||||
out.writeMedium(length);
|
||||
out.writeInt(spdySynReplyFrame.getStreamID());
|
||||
if (version < 3) {
|
||||
if (headerBlockLength == 0) {
|
||||
out.writeInt(0);
|
||||
} else {
|
||||
out.writeShort(0);
|
||||
}
|
||||
}
|
||||
out.writeBytes(data, data.readerIndex(), headerBlockLength);
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + 8);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_RST_STREAM_FRAME);
|
||||
out.writeInt(8);
|
||||
out.writeInt(spdyRstStreamFrame.getStreamID());
|
||||
out.writeInt(spdyRstStreamFrame.getStatus().getCode());
|
||||
|
||||
} 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;
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + length);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_SETTINGS_FRAME);
|
||||
out.writeByte(flags);
|
||||
out.writeMedium(length);
|
||||
out.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;
|
||||
}
|
||||
if (version < 3) {
|
||||
// Chromium Issue 79156
|
||||
// SPDY setting ids are not written in network byte order
|
||||
// Write id assuming the architecture is little endian
|
||||
out.writeByte(id >> 0 & 0xFF);
|
||||
out.writeByte(id >> 8 & 0xFF);
|
||||
out.writeByte(id >> 16 & 0xFF);
|
||||
out.writeByte(ID_flags);
|
||||
} else {
|
||||
out.writeByte(ID_flags);
|
||||
out.writeMedium(id);
|
||||
}
|
||||
out.writeInt(spdySettingsFrame.getValue(id));
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyNoOpFrame) {
|
||||
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_NOOP_FRAME);
|
||||
out.writeInt(0);
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + 4);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_PING_FRAME);
|
||||
out.writeInt(4);
|
||||
out.writeInt(spdyPingFrame.getID());
|
||||
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
|
||||
int length = version < 3 ? 4 : 8;
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + length);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_GOAWAY_FRAME);
|
||||
out.writeInt(length);
|
||||
out.writeInt(spdyGoAwayFrame.getLastGoodStreamID());
|
||||
if (version >= 3) {
|
||||
out.writeInt(spdyGoAwayFrame.getStatus().getCode());
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(version, spdyHeadersFrame));
|
||||
byte flags = spdyHeadersFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length;
|
||||
if (version < 3) {
|
||||
length = headerBlockLength == 0 ? 4 : 6 + headerBlockLength;
|
||||
} else {
|
||||
length = 4 + headerBlockLength;
|
||||
}
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + length);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_HEADERS_FRAME);
|
||||
out.writeByte(flags);
|
||||
out.writeMedium(length);
|
||||
out.writeInt(spdyHeadersFrame.getStreamID());
|
||||
if (version < 3 && headerBlockLength != 0) {
|
||||
out.writeShort(0);
|
||||
}
|
||||
out.writeBytes(data, data.readerIndex(), headerBlockLength);
|
||||
|
||||
} else if (msg instanceof SpdyWindowUpdateFrame) {
|
||||
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
|
||||
out.ensureWritableBytes(SPDY_HEADER_SIZE + 8);
|
||||
out.writeShort(version | 0x8000);
|
||||
out.writeShort(SPDY_WINDOW_UPDATE_FRAME);
|
||||
out.writeInt(8);
|
||||
out.writeInt(spdyWindowUpdateFrame.getStreamID());
|
||||
out.writeInt(spdyWindowUpdateFrame.getDeltaWindowSize());
|
||||
} else {
|
||||
throw new UnsupportedMessageTypeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeLengthField(int version, ChannelBuffer buffer, int length) {
|
||||
if (version < 3) {
|
||||
buffer.writeShort(length);
|
||||
} else {
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setLengthField(int version, ChannelBuffer buffer, int writerIndex, int length) {
|
||||
if (version < 3) {
|
||||
buffer.setShort(writerIndex, length);
|
||||
} else {
|
||||
buffer.setInt(writerIndex, length);
|
||||
}
|
||||
}
|
||||
|
||||
private static ChannelBuffer encodeHeaderBlock(int version, 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);
|
||||
writeLengthField(version, headerBlock, numHeaders);
|
||||
for (String name: names) {
|
||||
byte[] nameBytes = name.getBytes("UTF-8");
|
||||
writeLengthField(version, headerBlock, nameBytes.length);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
int savedIndex = headerBlock.writerIndex();
|
||||
int valueLength = 0;
|
||||
writeLengthField(version, headerBlock, 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);
|
||||
}
|
||||
setLengthField(version, headerBlock, 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;
|
||||
}
|
||||
ChannelBuffer compressed = ChannelBuffers.dynamicBuffer();
|
||||
synchronized (headerBlockCompressor) {
|
||||
if (!finished) {
|
||||
headerBlockCompressor.setInput(uncompressed);
|
||||
headerBlockCompressor.encode(compressed);
|
||||
}
|
||||
}
|
||||
return compressed;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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);
|
||||
|
||||
/**
|
||||
* Returns the status of this frame.
|
||||
*/
|
||||
SpdySessionStatus getStatus();
|
||||
|
||||
/**
|
||||
* Sets the status of this frame.
|
||||
*/
|
||||
void setStatus(SpdySessionStatus status);
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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,38 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.util.internal.DetectionUtil;
|
||||
|
||||
abstract class SpdyHeaderBlockCompressor {
|
||||
|
||||
static SpdyHeaderBlockCompressor newInstance(
|
||||
int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
|
||||
if (DetectionUtil.javaVersion() >= 7) {
|
||||
return new SpdyHeaderBlockZlibCompressor(
|
||||
version, compressionLevel);
|
||||
} else {
|
||||
return new SpdyHeaderBlockJZlibCompressor(
|
||||
version, compressionLevel, windowBits, memLevel);
|
||||
}
|
||||
}
|
||||
|
||||
abstract void setInput(ChannelBuffer decompressed);
|
||||
abstract void encode(ChannelBuffer compressed);
|
||||
abstract void end();
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
abstract class SpdyHeaderBlockDecompressor {
|
||||
|
||||
static SpdyHeaderBlockDecompressor newInstance(int version) {
|
||||
return new SpdyHeaderBlockZlibDecompressor(version);
|
||||
}
|
||||
|
||||
abstract void setInput(ChannelBuffer compressed);
|
||||
abstract void decode(ChannelBuffer decompressed) throws Exception;
|
||||
abstract void end();
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.handler.codec.compression.CompressionException;
|
||||
import io.netty.util.internal.jzlib.JZlib;
|
||||
import io.netty.util.internal.jzlib.ZStream;
|
||||
|
||||
class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
|
||||
private final ZStream z = new ZStream();
|
||||
|
||||
public SpdyHeaderBlockJZlibCompressor(
|
||||
int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||
throw new IllegalArgumentException(
|
||||
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||
}
|
||||
if (windowBits < 9 || windowBits > 15) {
|
||||
throw new IllegalArgumentException(
|
||||
"windowBits: " + windowBits + " (expected: 9-15)");
|
||||
}
|
||||
if (memLevel < 1 || memLevel > 9) {
|
||||
throw new IllegalArgumentException(
|
||||
"memLevel: " + memLevel + " (expected: 1-9)");
|
||||
}
|
||||
|
||||
int resultCode = z.deflateInit(
|
||||
compressionLevel, windowBits, memLevel, JZlib.W_ZLIB);
|
||||
if (resultCode != JZlib.Z_OK) {
|
||||
throw new CompressionException(
|
||||
"failed to initialize an SPDY header block deflater: " + resultCode);
|
||||
} else {
|
||||
if (version < 3) {
|
||||
resultCode = z.deflateSetDictionary(SPDY2_DICT, SPDY2_DICT.length);
|
||||
} else {
|
||||
resultCode = z.deflateSetDictionary(SPDY_DICT, SPDY_DICT.length);
|
||||
}
|
||||
if (resultCode != JZlib.Z_OK) {
|
||||
throw new CompressionException(
|
||||
"failed to set the SPDY dictionary: " + resultCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(ChannelBuffer decompressed) {
|
||||
byte[] in = new byte[decompressed.readableBytes()];
|
||||
decompressed.readBytes(in);
|
||||
z.next_in = in;
|
||||
z.next_in_index = 0;
|
||||
z.avail_in = in.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ChannelBuffer compressed) {
|
||||
try {
|
||||
byte[] out = new byte[(int) Math.ceil(z.next_in.length * 1.001) + 12];
|
||||
z.next_out = out;
|
||||
z.next_out_index = 0;
|
||||
z.avail_out = out.length;
|
||||
|
||||
int resultCode = z.deflate(JZlib.Z_SYNC_FLUSH);
|
||||
if (resultCode != JZlib.Z_OK) {
|
||||
throw new CompressionException("compression failure: " + resultCode);
|
||||
}
|
||||
|
||||
if (z.next_out_index != 0) {
|
||||
compressed.writeBytes(out, 0, z.next_out_index);
|
||||
}
|
||||
} finally {
|
||||
// Deference the external references explicitly to tell the VM that
|
||||
// the allocated byte arrays are temporary so that the call stack
|
||||
// can be utilized.
|
||||
// I'm not sure if the modern VMs do this optimization though.
|
||||
z.next_in = null;
|
||||
z.next_out = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
z.deflateEnd();
|
||||
z.next_in = null;
|
||||
z.next_out = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
|
||||
private final byte[] out = new byte[8192];
|
||||
private final Deflater compressor;
|
||||
|
||||
public SpdyHeaderBlockZlibCompressor(int version, int compressionLevel) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||
throw new IllegalArgumentException(
|
||||
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||
}
|
||||
compressor = new Deflater(compressionLevel);
|
||||
if (version < 3) {
|
||||
compressor.setDictionary(SPDY2_DICT);
|
||||
} else {
|
||||
compressor.setDictionary(SPDY_DICT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(ChannelBuffer decompressed) {
|
||||
byte[] in = new byte[decompressed.readableBytes()];
|
||||
decompressed.readBytes(in);
|
||||
compressor.setInput(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ChannelBuffer compressed) {
|
||||
while (!compressor.needsInput()) {
|
||||
int numBytes = compressor.deflate(out, 0, out.length, Deflater.SYNC_FLUSH);
|
||||
compressed.writeBytes(out, 0, numBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
compressor.end();
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
|
||||
|
||||
private final int version;
|
||||
private final byte[] out = new byte[8192];
|
||||
private final Inflater decompressor = new Inflater();
|
||||
|
||||
public SpdyHeaderBlockZlibDecompressor(int version) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(ChannelBuffer compressed) {
|
||||
byte[] in = new byte[compressed.readableBytes()];
|
||||
compressed.readBytes(in);
|
||||
decompressor.setInput(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ChannelBuffer decompressed) throws Exception {
|
||||
try {
|
||||
int numBytes = decompressor.inflate(out);
|
||||
if (numBytes == 0 && decompressor.needsDictionary()) {
|
||||
if (version < 3) {
|
||||
decompressor.setDictionary(SPDY2_DICT);
|
||||
} else {
|
||||
decompressor.setDictionary(SPDY_DICT);
|
||||
}
|
||||
numBytes = decompressor.inflate(out);
|
||||
}
|
||||
decompressed.writeBytes(out, 0, numBytes);
|
||||
} catch (DataFormatException e) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
decompressor.end();
|
||||
}
|
||||
}
|
@ -0,0 +1,770 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Provides the constants for the standard SPDY HTTP header names and commonly
|
||||
* used utility methods that access a {@link SpdyHeaderBlock}.
|
||||
* @apiviz.stereotype static
|
||||
*/
|
||||
public class SpdyHeaders {
|
||||
|
||||
/**
|
||||
* SPDY HTTP header names
|
||||
* @apiviz.stereotype static
|
||||
*/
|
||||
public static final class HttpNames {
|
||||
/**
|
||||
* {@code ":host"}
|
||||
*/
|
||||
public static final String HOST = ":host";
|
||||
/**
|
||||
* {@code ":method"}
|
||||
*/
|
||||
public static final String METHOD = ":method";
|
||||
/**
|
||||
* {@code ":path"}
|
||||
*/
|
||||
public static final String PATH = ":path";
|
||||
/**
|
||||
* {@code ":scheme"}
|
||||
*/
|
||||
public static final String SCHEME = ":scheme";
|
||||
/**
|
||||
* {@code ":status"}
|
||||
*/
|
||||
public static final String STATUS = ":status";
|
||||
/**
|
||||
* {@code ":version"}
|
||||
*/
|
||||
public static final String VERSION = ":version";
|
||||
|
||||
private HttpNames() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SPDY/2 HTTP header names
|
||||
* @apiviz.stereotype static
|
||||
*/
|
||||
public static final class Spdy2HttpNames {
|
||||
/**
|
||||
* {@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 Spdy2HttpNames() {
|
||||
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 SPDY host header.
|
||||
*/
|
||||
public static void removeHost(SpdyHeaderBlock block) {
|
||||
block.removeHeader(HttpNames.HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SPDY host header.
|
||||
*/
|
||||
public static String getHost(SpdyHeaderBlock block) {
|
||||
return block.getHeader(HttpNames.HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SPDY host header.
|
||||
*/
|
||||
public static void setHost(SpdyHeaderBlock block, String host) {
|
||||
block.setHeader(HttpNames.HOST, host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP method header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void removeMethod(SpdyHeaderBlock block) {
|
||||
removeMethod(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP method header.
|
||||
*/
|
||||
public static void removeMethod(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
block.removeHeader(Spdy2HttpNames.METHOD);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.METHOD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpMethod} represented by the HTTP method header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static HttpMethod getMethod(SpdyHeaderBlock block) {
|
||||
return getMethod(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpMethod} represented by the HTTP method header.
|
||||
*/
|
||||
public static HttpMethod getMethod(int spdyVersion, SpdyHeaderBlock block) {
|
||||
try {
|
||||
if (spdyVersion < 3) {
|
||||
return HttpMethod.valueOf(block.getHeader(Spdy2HttpNames.METHOD));
|
||||
} else {
|
||||
return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP method header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setMethod(SpdyHeaderBlock block, HttpMethod method) {
|
||||
setMethod(2, block, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP method header.
|
||||
*/
|
||||
public static void setMethod(int spdyVersion, SpdyHeaderBlock block, HttpMethod method) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.METHOD, method.getName());
|
||||
} else {
|
||||
block.setHeader(HttpNames.METHOD, method.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the URL scheme header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void removeScheme(SpdyHeaderBlock block) {
|
||||
removeMethod(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the URL scheme header.
|
||||
*/
|
||||
public static void removeScheme(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 2) {
|
||||
block.removeHeader(Spdy2HttpNames.SCHEME);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.SCHEME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the URL scheme header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getScheme(SpdyHeaderBlock block) {
|
||||
return getScheme(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the URL scheme header.
|
||||
*/
|
||||
public static String getScheme(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
return block.getHeader(Spdy2HttpNames.SCHEME);
|
||||
} else {
|
||||
return block.getHeader(HttpNames.SCHEME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL scheme header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setScheme(SpdyHeaderBlock block, String scheme) {
|
||||
setScheme(2, block, scheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL scheme header.
|
||||
*/
|
||||
public static void setScheme(int spdyVersion, SpdyHeaderBlock block, String scheme) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.SCHEME, scheme);
|
||||
} else {
|
||||
block.setHeader(HttpNames.SCHEME, scheme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP response status header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void removeStatus(SpdyHeaderBlock block) {
|
||||
removeMethod(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP response status header.
|
||||
*/
|
||||
public static void removeStatus(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
block.removeHeader(Spdy2HttpNames.STATUS);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static HttpResponseStatus getStatus(SpdyHeaderBlock block) {
|
||||
return getStatus(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
|
||||
*/
|
||||
public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeaderBlock block) {
|
||||
try {
|
||||
String status;
|
||||
if (spdyVersion < 3) {
|
||||
status = block.getHeader(Spdy2HttpNames.STATUS);
|
||||
} else {
|
||||
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(reasonPhrase)) {
|
||||
return responseStatus;
|
||||
} else {
|
||||
return new HttpResponseStatus(code, reasonPhrase);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP response status header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setStatus(SpdyHeaderBlock block, HttpResponseStatus status) {
|
||||
setStatus(2, block, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP response status header.
|
||||
*/
|
||||
public static void setStatus(int spdyVersion, SpdyHeaderBlock block, HttpResponseStatus status) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.STATUS, status.toString());
|
||||
} else {
|
||||
block.setHeader(HttpNames.STATUS, status.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the URL path header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void removeUrl(SpdyHeaderBlock block) {
|
||||
removeUrl(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the URL path header.
|
||||
*/
|
||||
public static void removeUrl(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
block.removeHeader(Spdy2HttpNames.URL);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.PATH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the URL path header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getUrl(SpdyHeaderBlock block) {
|
||||
return getUrl(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the URL path header.
|
||||
*/
|
||||
public static String getUrl(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
return block.getHeader(Spdy2HttpNames.URL);
|
||||
} else {
|
||||
return block.getHeader(HttpNames.PATH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL path header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setUrl(SpdyHeaderBlock block, String path) {
|
||||
setUrl(2, block, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL path header.
|
||||
*/
|
||||
public static void setUrl(int spdyVersion, SpdyHeaderBlock block, String path) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.URL, path);
|
||||
} else {
|
||||
block.setHeader(HttpNames.PATH, path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP version header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void removeVersion(SpdyHeaderBlock block) {
|
||||
removeVersion(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the HTTP version header.
|
||||
*/
|
||||
public static void removeVersion(int spdyVersion, SpdyHeaderBlock block) {
|
||||
if (spdyVersion < 3) {
|
||||
block.removeHeader(Spdy2HttpNames.VERSION);
|
||||
} else {
|
||||
block.removeHeader(HttpNames.VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpVersion} represented by the HTTP version header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static HttpVersion getVersion(SpdyHeaderBlock block) {
|
||||
return getVersion(2, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpVersion} represented by the HTTP version header.
|
||||
*/
|
||||
public static HttpVersion getVersion(int spdyVersion, SpdyHeaderBlock block) {
|
||||
try {
|
||||
if (spdyVersion < 3) {
|
||||
return HttpVersion.valueOf(block.getHeader(Spdy2HttpNames.VERSION));
|
||||
} else {
|
||||
return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP version header.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setVersion(SpdyHeaderBlock block, HttpVersion httpVersion) {
|
||||
setVersion(2, block, httpVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP version header.
|
||||
*/
|
||||
public static void setVersion(int spdyVersion, SpdyHeaderBlock block, HttpVersion httpVersion) {
|
||||
if (spdyVersion < 3) {
|
||||
block.setHeader(Spdy2HttpNames.VERSION, httpVersion.getText());
|
||||
} else {
|
||||
block.setHeader(HttpNames.VERSION, httpVersion.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
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,43 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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);
|
||||
|
||||
/**
|
||||
* 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,35 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.channel.CombinedChannelHandler;
|
||||
|
||||
/**
|
||||
* A combination of {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}
|
||||
* @apiviz.has io.netty.handler.codec.sdpy.SpdyHttpDecoder
|
||||
* @apiviz.has io.netty.handler.codec.spdy.SpdyHttpEncoder
|
||||
*/
|
||||
public class SpdyHttpCodec extends CombinedChannelHandler {
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified decoder options.
|
||||
*/
|
||||
public SpdyHttpCodec(int version, int maxContentLength) {
|
||||
super(
|
||||
new SpdyHttpDecoder(version, maxContentLength),
|
||||
new SpdyHttpEncoder(version));
|
||||
}
|
||||
}
|
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.ChannelInboundHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.handler.codec.http.DefaultHttpRequest;
|
||||
import io.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
|
||||
* and {@link SpdyDataFrame}s into {@link HttpRequest}s and {@link HttpResponse}s.
|
||||
*/
|
||||
public class SpdyHttpDecoder extends MessageToMessageDecoder<Object, HttpMessage> {
|
||||
|
||||
private final int spdyVersion;
|
||||
private final int maxContentLength;
|
||||
private final Map<Integer, HttpMessage> messageMap = new HashMap<Integer, HttpMessage>();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param version the protocol version
|
||||
* @param maxContentLength the maximum length of the message content.
|
||||
* If the length of the message content exceeds this value,
|
||||
* a {@link TooLongFrameException} will be raised.
|
||||
*/
|
||||
public SpdyHttpDecoder(int version, int maxContentLength) {
|
||||
super();
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
if (maxContentLength <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxContentLength must be a positive integer: " + maxContentLength);
|
||||
}
|
||||
spdyVersion = version;
|
||||
this.maxContentLength = maxContentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpMessage decode(ChannelInboundHandlerContext<Object> ctx, Object msg) throws Exception {
|
||||
if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
// HTTP requests/responses are mapped one-to-one to SPDY streams.
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
int streamID = spdySynStreamFrame.getStreamID();
|
||||
|
||||
if (SpdyCodecUtil.isServerID(streamID)) {
|
||||
// SYN_STREAM frames initiated by the server are pushed resources
|
||||
int associatedToStreamID = spdySynStreamFrame.getAssociatedToStreamID();
|
||||
|
||||
// If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0
|
||||
// it must reply with a RST_STREAM with error code INVALID_STREAM
|
||||
if (associatedToStreamID == 0) {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamID, SpdyStreamStatus.INVALID_STREAM);
|
||||
ctx.write(spdyRstStreamFrame);
|
||||
}
|
||||
|
||||
String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame);
|
||||
|
||||
// If a client receives a SYN_STREAM without a 'url' header
|
||||
// it must reply with a RST_STREAM with error code PROTOCOL_ERROR
|
||||
if (URL == null) {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
ctx.write(spdyRstStreamFrame);
|
||||
}
|
||||
|
||||
try {
|
||||
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynStreamFrame);
|
||||
|
||||
// Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
|
||||
SpdyHttpHeaders.setStreamID(httpResponse, streamID);
|
||||
SpdyHttpHeaders.setAssociatedToStreamID(httpResponse, associatedToStreamID);
|
||||
SpdyHttpHeaders.setPriority(httpResponse, spdySynStreamFrame.getPriority());
|
||||
SpdyHttpHeaders.setUrl(httpResponse, URL);
|
||||
|
||||
if (spdySynStreamFrame.isLast()) {
|
||||
HttpHeaders.setContentLength(httpResponse, 0);
|
||||
return httpResponse;
|
||||
} else {
|
||||
// Response body will follow in a series of Data Frames
|
||||
messageMap.put(new Integer(streamID), httpResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
ctx.write(spdyRstStreamFrame);
|
||||
}
|
||||
|
||||
} else {
|
||||
// SYN_STREAM frames initiated by the client are HTTP requests
|
||||
try {
|
||||
HttpRequest httpRequest = createHttpRequest(spdyVersion, spdySynStreamFrame);
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
SpdyHttpHeaders.setStreamID(httpRequest, streamID);
|
||||
|
||||
if (spdySynStreamFrame.isLast()) {
|
||||
return httpRequest;
|
||||
} else {
|
||||
// Request body will follow in a series of Data Frames
|
||||
messageMap.put(new Integer(streamID), httpRequest);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// If a client sends a SYN_STREAM without all of the method, url (host and path),
|
||||
// scheme, and version headers the server must reply with a HTTP 400 BAD REQUEST reply.
|
||||
// Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||
spdySynReplyFrame.setLast(true);
|
||||
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
|
||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
|
||||
ctx.write(spdySynReplyFrame);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
int streamID = spdySynReplyFrame.getStreamID();
|
||||
|
||||
try {
|
||||
HttpResponse httpResponse = createHttpResponse(spdyVersion, spdySynReplyFrame);
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
SpdyHttpHeaders.setStreamID(httpResponse, streamID);
|
||||
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
HttpHeaders.setContentLength(httpResponse, 0);
|
||||
return httpResponse;
|
||||
} else {
|
||||
// Response body will follow in a series of Data Frames
|
||||
messageMap.put(new Integer(streamID), httpResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// If a client receives a SYN_REPLY without valid status and version headers
|
||||
// the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamID, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
ctx.write(spdyRstStreamFrame);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
Integer streamID = new Integer(spdyHeadersFrame.getStreamID());
|
||||
HttpMessage httpMessage = messageMap.get(streamID);
|
||||
|
||||
// If message is not in map discard HEADERS frame.
|
||||
// SpdySessionHandler should prevent this from happening.
|
||||
if (httpMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> e: spdyHeadersFrame.getHeaders()) {
|
||||
httpMessage.addHeader(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
Integer streamID = new Integer(spdyDataFrame.getStreamID());
|
||||
HttpMessage httpMessage = messageMap.get(streamID);
|
||||
|
||||
// If message is not in map discard Data Frame.
|
||||
// SpdySessionHandler should prevent this from happening.
|
||||
if (httpMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ChannelBuffer content = httpMessage.getContent();
|
||||
if (content.readableBytes() > maxContentLength - spdyDataFrame.getData().readableBytes()) {
|
||||
messageMap.remove(streamID);
|
||||
throw new TooLongFrameException(
|
||||
"HTTP content length exceeded " + maxContentLength + " bytes.");
|
||||
}
|
||||
|
||||
ChannelBuffer spdyDataFrameData = spdyDataFrame.getData();
|
||||
int spdyDataFrameDataLen = spdyDataFrameData.readableBytes();
|
||||
if (content == ChannelBuffers.EMPTY_BUFFER) {
|
||||
content = ChannelBuffers.dynamicBuffer(spdyDataFrameDataLen);
|
||||
content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);
|
||||
httpMessage.setContent(content);
|
||||
} else {
|
||||
content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);
|
||||
}
|
||||
|
||||
if (spdyDataFrame.isLast()) {
|
||||
HttpHeaders.setContentLength(httpMessage, content.readableBytes());
|
||||
messageMap.remove(streamID);
|
||||
return httpMessage;
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
Integer streamID = new Integer(spdyRstStreamFrame.getStreamID());
|
||||
messageMap.remove(streamID);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HttpRequest createHttpRequest(int spdyVersion, SpdyHeaderBlock requestFrame)
|
||||
throws Exception {
|
||||
// Create the first line of the request from the name/value pairs
|
||||
HttpMethod method = SpdyHeaders.getMethod(spdyVersion, requestFrame);
|
||||
String url = SpdyHeaders.getUrl(spdyVersion, requestFrame);
|
||||
HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame);
|
||||
SpdyHeaders.removeMethod(spdyVersion, requestFrame);
|
||||
SpdyHeaders.removeUrl(spdyVersion, requestFrame);
|
||||
SpdyHeaders.removeVersion(spdyVersion, requestFrame);
|
||||
|
||||
HttpRequest httpRequest = new DefaultHttpRequest(httpVersion, method, url);
|
||||
|
||||
// Remove the scheme header
|
||||
SpdyHeaders.removeScheme(spdyVersion, requestFrame);
|
||||
|
||||
if (spdyVersion >= 3) {
|
||||
// Replace the SPDY host header with the HTTP host header
|
||||
String host = SpdyHeaders.getHost(requestFrame);
|
||||
SpdyHeaders.removeHost(requestFrame);
|
||||
HttpHeaders.setHost(httpRequest, host);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> e: requestFrame.getHeaders()) {
|
||||
httpRequest.addHeader(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
// The Connection and Keep-Alive headers are no longer valid
|
||||
HttpHeaders.setKeepAlive(httpRequest, true);
|
||||
|
||||
// Transfer-Encoding header is not valid
|
||||
httpRequest.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
private static HttpResponse createHttpResponse(int spdyVersion, SpdyHeaderBlock responseFrame)
|
||||
throws Exception {
|
||||
// Create the first line of the response from the name/value pairs
|
||||
HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame);
|
||||
HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame);
|
||||
SpdyHeaders.removeStatus(spdyVersion, responseFrame);
|
||||
SpdyHeaders.removeVersion(spdyVersion, responseFrame);
|
||||
|
||||
HttpResponse httpResponse = new DefaultHttpResponse(version, status);
|
||||
for (Map.Entry<String, String> e: responseFrame.getHeaders()) {
|
||||
httpResponse.addHeader(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
// The Connection and Keep-Alive headers are no longer valid
|
||||
HttpHeaders.setKeepAlive(httpResponse, true);
|
||||
|
||||
// Transfer-Encoding header is not valid
|
||||
httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
httpResponse.removeHeader(HttpHeaders.Names.TRAILER);
|
||||
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
import io.netty.channel.ChannelOutboundHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
||||
import io.netty.handler.codec.http.HttpChunk;
|
||||
import io.netty.handler.codec.http.HttpChunkTrailer;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpChunk}s
|
||||
* into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s.
|
||||
*
|
||||
* <h3>Request Annotations</h3>
|
||||
*
|
||||
* SPDY specific headers must be added to {@link HttpRequest}s:
|
||||
* <table border=1>
|
||||
* <tr>
|
||||
* <th>Header Name</th><th>Header Value</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Stream-ID"}</td>
|
||||
* <td>The Stream-ID for this request.
|
||||
* Stream-IDs must be odd, positive integers, and must increase monotonically.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Priority"}</td>
|
||||
* <td>The priority value for this request.
|
||||
* The priority should be between 0 and 7 inclusive.
|
||||
* 0 represents the highest priority and 7 represents the lowest.
|
||||
* This header is optional and defaults to 0.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <h3>Response Annotations</h3>
|
||||
*
|
||||
* SPDY specific headers must be added to {@link HttpResponse}s:
|
||||
* <table border=1>
|
||||
* <tr>
|
||||
* <th>Header Name</th><th>Header Value</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Stream-ID"}</td>
|
||||
* <td>The Stream-ID of the request corresponding to this response.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <h3>Pushed Resource Annotations</h3>
|
||||
*
|
||||
* SPDY specific headers must be added to pushed {@link HttpResponse}s:
|
||||
* <table border=1>
|
||||
* <tr>
|
||||
* <th>Header Name</th><th>Header Value</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Stream-ID"}</td>
|
||||
* <td>The Stream-ID for this resource.
|
||||
* Stream-IDs must be even, positive integers, and must increase monotonically.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Associated-To-Stream-ID"}</td>
|
||||
* <td>The Stream-ID of the request that initiated this pushed resource.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-Priority"}</td>
|
||||
* <td>The priority value for this resource.
|
||||
* The priority should be between 0 and 7 inclusive.
|
||||
* 0 represents the highest priority and 7 represents the lowest.
|
||||
* This header is optional and defaults to 0.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code "X-SPDY-URL"}</td>
|
||||
* <td>The absolute path for the resource being pushed.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <h3>Required Annotations</h3>
|
||||
*
|
||||
* SPDY requires that all Requests and Pushed Resources contain
|
||||
* an HTTP "Host" header.
|
||||
*
|
||||
* <h3>Optional Annotations</h3>
|
||||
*
|
||||
* Requests and Pushed Resources must contain a SPDY scheme header.
|
||||
* This can be set via the {@code "X-SPDY-Scheme"} header but otherwise
|
||||
* defaults to "https" as that is the most common SPDY deployment.
|
||||
*
|
||||
* <h3>Chunked Content</h3>
|
||||
*
|
||||
* This encoder associates all {@link HttpChunk}s that it receives
|
||||
* with the most recently received 'chunked' {@link HttpRequest}
|
||||
* or {@link HttpResponse}.
|
||||
*
|
||||
* <h3>Pushed Resources</h3>
|
||||
*
|
||||
* All pushed resources should be sent before sending the response
|
||||
* that corresponds to the initial request.
|
||||
*/
|
||||
public class SpdyHttpEncoder extends MessageToMessageEncoder<Object, Object> {
|
||||
|
||||
private final int spdyVersion;
|
||||
private volatile int currentStreamID;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param version the protocol version
|
||||
*/
|
||||
public SpdyHttpEncoder(int version) {
|
||||
if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
spdyVersion = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncodable(Object msg) throws Exception {
|
||||
return msg instanceof HttpRequest ||
|
||||
msg instanceof HttpResponse ||
|
||||
msg instanceof HttpChunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object encode(ChannelOutboundHandlerContext<Object> ctx, Object msg) throws Exception {
|
||||
|
||||
List<Object> out = new ArrayList<Object>();
|
||||
if (msg instanceof HttpRequest) {
|
||||
|
||||
HttpRequest httpRequest = (HttpRequest) msg;
|
||||
SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest);
|
||||
int streamID = spdySynStreamFrame.getStreamID();
|
||||
out.add(spdySynStreamFrame);
|
||||
addContent(out, streamID, httpRequest);
|
||||
|
||||
} else if (msg instanceof HttpResponse) {
|
||||
|
||||
HttpResponse httpResponse = (HttpResponse) msg;
|
||||
if (httpResponse.containsHeader(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID)) {
|
||||
SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpResponse);
|
||||
int streamID = spdySynStreamFrame.getStreamID();
|
||||
out.add(spdySynStreamFrame);
|
||||
addContent(out, streamID, httpResponse);
|
||||
} else {
|
||||
SpdySynReplyFrame spdySynReplyFrame = createSynReplyFrame(httpResponse);
|
||||
int streamID = spdySynReplyFrame.getStreamID();
|
||||
out.add(spdySynReplyFrame);
|
||||
addContent(out, streamID, httpResponse);
|
||||
}
|
||||
|
||||
} else if (msg instanceof HttpChunk) {
|
||||
|
||||
HttpChunk chunk = (HttpChunk) msg;
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamID);
|
||||
spdyDataFrame.setData(chunk.getContent());
|
||||
spdyDataFrame.setLast(chunk.isLast());
|
||||
|
||||
if (chunk instanceof HttpChunkTrailer) {
|
||||
HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
|
||||
List<Map.Entry<String, String>> trailers = trailer.getHeaders();
|
||||
if (trailers.isEmpty()) {
|
||||
out.add(spdyDataFrame);
|
||||
} else {
|
||||
// Create SPDY HEADERS frame out of trailers
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamID);
|
||||
for (Map.Entry<String, String> entry: trailers) {
|
||||
spdyHeadersFrame.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
// Write HEADERS frame and append Data Frame
|
||||
out.add(spdyHeadersFrame);
|
||||
out.add(spdyDataFrame);
|
||||
}
|
||||
} else {
|
||||
out.add(spdyDataFrame);
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedMessageTypeException(msg);
|
||||
}
|
||||
|
||||
return out.toArray();
|
||||
}
|
||||
|
||||
private static void addContent(List<Object> out, int streamID, HttpMessage httpMessage) {
|
||||
if (!httpMessage.getContent().readable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create SPDY Data Frame out of message content
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
spdyDataFrame.setData(httpMessage.getContent());
|
||||
spdyDataFrame.setLast(true);
|
||||
|
||||
out.add(spdyDataFrame);
|
||||
}
|
||||
|
||||
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage)
|
||||
throws Exception {
|
||||
boolean chunked = httpMessage.isChunked();
|
||||
|
||||
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
|
||||
int streamID = SpdyHttpHeaders.getStreamID(httpMessage);
|
||||
int associatedToStreamID = SpdyHttpHeaders.getAssociatedToStreamID(httpMessage);
|
||||
byte priority = SpdyHttpHeaders.getPriority(httpMessage);
|
||||
String URL = SpdyHttpHeaders.getUrl(httpMessage);
|
||||
String scheme = SpdyHttpHeaders.getScheme(httpMessage);
|
||||
SpdyHttpHeaders.removeStreamID(httpMessage);
|
||||
SpdyHttpHeaders.removeAssociatedToStreamID(httpMessage);
|
||||
SpdyHttpHeaders.removePriority(httpMessage);
|
||||
SpdyHttpHeaders.removeUrl(httpMessage);
|
||||
SpdyHttpHeaders.removeScheme(httpMessage);
|
||||
|
||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
||||
// headers are not valid and MUST not be sent.
|
||||
httpMessage.removeHeader(HttpHeaders.Names.CONNECTION);
|
||||
httpMessage.removeHeader("Keep-Alive");
|
||||
httpMessage.removeHeader("Proxy-Connection");
|
||||
httpMessage.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
|
||||
|
||||
// Unfold the first line of the message into name/value pairs
|
||||
if (httpMessage instanceof HttpRequest) {
|
||||
HttpRequest httpRequest = (HttpRequest) httpMessage;
|
||||
SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
|
||||
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
|
||||
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
|
||||
}
|
||||
if (httpMessage instanceof HttpResponse) {
|
||||
HttpResponse httpResponse = (HttpResponse) httpMessage;
|
||||
SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
|
||||
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
|
||||
spdySynStreamFrame.setUnidirectional(true);
|
||||
}
|
||||
|
||||
// Replace the HTTP host header with the SPDY host header
|
||||
if (spdyVersion >= 3) {
|
||||
String host = HttpHeaders.getHost(httpMessage);
|
||||
httpMessage.removeHeader(HttpHeaders.Names.HOST);
|
||||
SpdyHeaders.setHost(spdySynStreamFrame, host);
|
||||
}
|
||||
|
||||
// Set the SPDY scheme header
|
||||
if (scheme == null) {
|
||||
scheme = "https";
|
||||
}
|
||||
SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme);
|
||||
|
||||
// Transfer the remaining HTTP headers
|
||||
for (Map.Entry<String, String> entry: httpMessage.getHeaders()) {
|
||||
spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (chunked) {
|
||||
currentStreamID = streamID;
|
||||
spdySynStreamFrame.setLast(false);
|
||||
} else {
|
||||
spdySynStreamFrame.setLast(httpMessage.getContent().readableBytes() == 0);
|
||||
}
|
||||
|
||||
return spdySynStreamFrame;
|
||||
}
|
||||
|
||||
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse)
|
||||
throws Exception {
|
||||
boolean chunked = httpResponse.isChunked();
|
||||
|
||||
// Get the Stream-ID from the headers
|
||||
int streamID = SpdyHttpHeaders.getStreamID(httpResponse);
|
||||
SpdyHttpHeaders.removeStreamID(httpResponse);
|
||||
|
||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-ENcoding
|
||||
// headers are not valid and MUST not be sent.
|
||||
httpResponse.removeHeader(HttpHeaders.Names.CONNECTION);
|
||||
httpResponse.removeHeader("Keep-Alive");
|
||||
httpResponse.removeHeader("Proxy-Connection");
|
||||
httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||
|
||||
// Unfold the first line of the response into name/value pairs
|
||||
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus());
|
||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion());
|
||||
|
||||
// Transfer the remaining HTTP headers
|
||||
for (Map.Entry<String, String> entry: httpResponse.getHeaders()) {
|
||||
spdySynReplyFrame.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (chunked) {
|
||||
currentStreamID = streamID;
|
||||
spdySynReplyFrame.setLast(false);
|
||||
} else {
|
||||
spdySynReplyFrame.setLast(httpResponse.getContent().readableBytes() == 0);
|
||||
}
|
||||
|
||||
return spdySynReplyFrame;
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
|
||||
/**
|
||||
* Provides the constants for the header names and the utility methods
|
||||
* used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}.
|
||||
* @apiviz.stereotype static
|
||||
*/
|
||||
public final class SpdyHttpHeaders {
|
||||
|
||||
/**
|
||||
* SPDY HTTP header names
|
||||
* @apiviz.stereotype static
|
||||
*/
|
||||
public static final class Names {
|
||||
/**
|
||||
* {@code "X-SPDY-Stream-ID"}
|
||||
*/
|
||||
public static final String STREAM_ID = "X-SPDY-Stream-ID";
|
||||
/**
|
||||
* {@code "X-SPDY-Associated-To-Stream-ID"}
|
||||
*/
|
||||
public static final String ASSOCIATED_TO_STREAM_ID = "X-SPDY-Associated-To-Stream-ID";
|
||||
/**
|
||||
* {@code "X-SPDY-Priority"}
|
||||
*/
|
||||
public static final String PRIORITY = "X-SPDY-Priority";
|
||||
/**
|
||||
* {@code "X-SPDY-URL"}
|
||||
*/
|
||||
public static final String URL = "X-SPDY-URL";
|
||||
/**
|
||||
* {@code "X-SPDY-Scheme"}
|
||||
*/
|
||||
public static final String SCHEME = "X-SPDY-Scheme";
|
||||
|
||||
private Names() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
private SpdyHttpHeaders() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Stream-ID"} header.
|
||||
*/
|
||||
public static void removeStreamID(HttpMessage message) {
|
||||
message.removeHeader(Names.STREAM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Stream-ID"} header.
|
||||
*/
|
||||
public static int getStreamID(HttpMessage message) {
|
||||
return HttpHeaders.getIntHeader(message, Names.STREAM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Stream-ID"} header.
|
||||
*/
|
||||
public static void setStreamID(HttpMessage message, int streamID) {
|
||||
HttpHeaders.setIntHeader(message, Names.STREAM_ID, streamID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Associated-To-Stream-ID"} header.
|
||||
*/
|
||||
public static void removeAssociatedToStreamID(HttpMessage message) {
|
||||
message.removeHeader(Names.ASSOCIATED_TO_STREAM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Associated-To-Stream-ID"} header.
|
||||
*
|
||||
* @return the header value or {@code 0} if there is no such header or
|
||||
* if the header value is not a number
|
||||
*/
|
||||
public static int getAssociatedToStreamID(HttpMessage message) {
|
||||
return HttpHeaders.getIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Associated-To-Stream-ID"} header.
|
||||
*/
|
||||
public static void setAssociatedToStreamID(HttpMessage message, int associatedToStreamID) {
|
||||
HttpHeaders.setIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Priority"} header.
|
||||
*/
|
||||
public static void removePriority(HttpMessage message) {
|
||||
message.removeHeader(Names.PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Priority"} header.
|
||||
*
|
||||
* @return the header value or {@code 0} if there is no such header or
|
||||
* if the header value is not a number
|
||||
*/
|
||||
public static byte getPriority(HttpMessage message) {
|
||||
return (byte) HttpHeaders.getIntHeader(message, Names.PRIORITY, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Priority"} header.
|
||||
*/
|
||||
public static void setPriority(HttpMessage message, byte priority) {
|
||||
HttpHeaders.setIntHeader(message, Names.PRIORITY, priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-URL"} header.
|
||||
*/
|
||||
public static void removeUrl(HttpMessage message) {
|
||||
message.removeHeader(Names.URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-URL"} header.
|
||||
*/
|
||||
public static String getUrl(HttpMessage message) {
|
||||
return message.getHeader(Names.URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-URL"} header.
|
||||
*/
|
||||
public static void setUrl(HttpMessage message, String url) {
|
||||
message.setHeader(Names.URL, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static void removeScheme(HttpMessage message) {
|
||||
message.removeHeader(Names.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static String getScheme(HttpMessage message) {
|
||||
return message.getHeader(Names.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static void setScheme(HttpMessage message, String scheme) {
|
||||
message.setHeader(Names.URL, scheme);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol NOOP Control Frame
|
||||
*/
|
||||
public interface SpdyNoOpFrame {
|
||||
// Tag interface
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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,54 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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 {
|
||||
|
||||
private static final long serialVersionUID = 7870000537743847264L;
|
||||
|
||||
/**
|
||||
* 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 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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,114 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* The SPDY session status code and its description.
|
||||
* @apiviz.exclude
|
||||
*/
|
||||
public class SpdySessionStatus implements Comparable<SpdySessionStatus> {
|
||||
|
||||
/**
|
||||
* 0 OK
|
||||
*/
|
||||
public static final SpdySessionStatus OK =
|
||||
new SpdySessionStatus(0, "OK");
|
||||
|
||||
/**
|
||||
* 1 Protocol Error
|
||||
*/
|
||||
public static final SpdySessionStatus PROTOCOL_ERROR =
|
||||
new SpdySessionStatus(1, "PROTOCOL_ERROR");
|
||||
|
||||
/**
|
||||
* 11 Internal Error
|
||||
*/
|
||||
public static final SpdySessionStatus INTERNAL_ERROR =
|
||||
new SpdySessionStatus(11, "INTERNAL_ERROR");
|
||||
|
||||
/**
|
||||
* Returns the {@link SpdySessionStatus} 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 SpdySessionStatus valueOf(int code) {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return OK;
|
||||
case 1:
|
||||
return PROTOCOL_ERROR;
|
||||
case 11:
|
||||
return INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
return new SpdySessionStatus(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 SpdySessionStatus(int code, String statusPhrase) {
|
||||
if (statusPhrase == null) {
|
||||
throw new NullPointerException("statusPhrase");
|
||||
}
|
||||
|
||||
this.code = code;
|
||||
this.statusPhrase = statusPhrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the code of this status.
|
||||
*/
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status phrase of this status.
|
||||
*/
|
||||
public String getStatusPhrase() {
|
||||
return statusPhrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SpdySessionStatus)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getCode() == ((SpdySessionStatus) o).getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getStatusPhrase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SpdySessionStatus o) {
|
||||
return getCode() - o.getCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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;
|
||||
int SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
|
||||
|
||||
/**
|
||||
* 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 exceed 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,188 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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");
|
||||
|
||||
/**
|
||||
* 8 Stream In Use
|
||||
*/
|
||||
public static final SpdyStreamStatus STREAM_IN_USE =
|
||||
new SpdyStreamStatus(8, "STREAM_IN_USE");
|
||||
|
||||
/**
|
||||
* 9 Stream Already Closed
|
||||
*/
|
||||
public static final SpdyStreamStatus STREAM_ALREADY_CLOSED =
|
||||
new SpdyStreamStatus(9, "STREAM_ALREADY_CLOSED");
|
||||
|
||||
/**
|
||||
* 10 Invalid Credentials
|
||||
*/
|
||||
public static final SpdyStreamStatus INVALID_CREDENTIALS =
|
||||
new SpdyStreamStatus(10, "INVALID_CREDENTIALS");
|
||||
|
||||
/**
|
||||
* 11 Frame Too Large
|
||||
*/
|
||||
public static final SpdyStreamStatus FRAME_TOO_LARGE =
|
||||
new SpdyStreamStatus(11, "FRAME_TOO_LARGE");
|
||||
|
||||
/**
|
||||
* 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;
|
||||
case 8:
|
||||
return STREAM_IN_USE;
|
||||
case 9:
|
||||
return STREAM_ALREADY_CLOSED;
|
||||
case 10:
|
||||
return INVALID_CREDENTIALS;
|
||||
case 11:
|
||||
return FRAME_TOO_LARGE;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SpdyStreamStatus o) {
|
||||
return getCode() - o.getCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.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 7 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,43 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol WINDOW_UPDATE Control Frame
|
||||
*/
|
||||
public interface SpdyWindowUpdateFrame {
|
||||
|
||||
/**
|
||||
* 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 Delta-Window-Size of this frame.
|
||||
*/
|
||||
int getDeltaWindowSize();
|
||||
|
||||
/**
|
||||
* Sets the Delta-Window-Size of this frame.
|
||||
* The Delta-Window-Size must be positive.
|
||||
*/
|
||||
void setDeltaWindowSize(int deltaWindowSize);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/**
|
||||
* Encoder, decoder, 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 io.netty.handler.codec.spdy;
|
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
import static org.junit.Assert.*;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInboundHandlerContext;
|
||||
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
|
||||
import io.netty.channel.ChannelInboundStreamHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.util.SocketAddresses;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public abstract class AbstractSocketSpdyEchoTest {
|
||||
|
||||
private static final Random random = new Random();
|
||||
static final int ignoredBytes = 20;
|
||||
|
||||
private static ChannelBuffer createFrames(int version) {
|
||||
int length = version < 3 ? 1176 : 1174;
|
||||
ChannelBuffer frames = ChannelBuffers.buffer(length);
|
||||
|
||||
// SPDY UNKNOWN Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(0xFFFF);
|
||||
frames.writeByte(0xFF);
|
||||
frames.writeMedium(4);
|
||||
frames.writeInt(random.nextInt());
|
||||
|
||||
// SPDY NOOP Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(5);
|
||||
frames.writeInt(0);
|
||||
|
||||
// SPDY Data Frame
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
frames.writeByte(0x01);
|
||||
frames.writeMedium(1024);
|
||||
for (int i = 0; i < 256; i ++) {
|
||||
frames.writeInt(random.nextInt());
|
||||
}
|
||||
|
||||
// SPDY SYN_STREAM Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(1);
|
||||
frames.writeByte(0x03);
|
||||
if (version < 3) {
|
||||
frames.writeMedium(12);
|
||||
} else {
|
||||
frames.writeMedium(10);
|
||||
}
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF);
|
||||
frames.writeShort(0x8000);
|
||||
if (version < 3) {
|
||||
frames.writeShort(0);
|
||||
}
|
||||
|
||||
// SPDY SYN_REPLY Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(2);
|
||||
frames.writeByte(0x01);
|
||||
if (version < 3) {
|
||||
frames.writeMedium(8);
|
||||
} else {
|
||||
frames.writeMedium(4);
|
||||
}
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
if (version < 3) {
|
||||
frames.writeInt(0);
|
||||
}
|
||||
|
||||
// SPDY RST_STREAM Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(3);
|
||||
frames.writeInt(8);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
frames.writeInt(random.nextInt() | 0x01);
|
||||
|
||||
// SPDY SETTINGS Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(4);
|
||||
frames.writeByte(0x01);
|
||||
frames.writeMedium(12);
|
||||
frames.writeInt(1);
|
||||
if (version < 3) {
|
||||
frames.writeMedium(random.nextInt());
|
||||
frames.writeByte(0x03);
|
||||
} else {
|
||||
frames.writeByte(0x03);
|
||||
frames.writeMedium(random.nextInt());
|
||||
}
|
||||
frames.writeInt(random.nextInt());
|
||||
|
||||
// SPDY PING Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(6);
|
||||
frames.writeInt(4);
|
||||
frames.writeInt(random.nextInt());
|
||||
|
||||
// SPDY GOAWAY Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(7);
|
||||
if (version < 3) {
|
||||
frames.writeInt(4);
|
||||
} else {
|
||||
frames.writeInt(8);
|
||||
}
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF);
|
||||
if (version >= 3) {
|
||||
frames.writeInt(random.nextInt() | 0x01);
|
||||
}
|
||||
|
||||
// SPDY HEADERS Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(8);
|
||||
frames.writeByte(0x01);
|
||||
frames.writeMedium(4);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
|
||||
// SPDY WINDOW_UPDATE Frame
|
||||
frames.writeByte(0x80);
|
||||
frames.writeByte(version);
|
||||
frames.writeShort(9);
|
||||
frames.writeInt(8);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
private ServerBootstrap sb;
|
||||
private Bootstrap cb;
|
||||
|
||||
protected abstract ServerBootstrap newServerBootstrap();
|
||||
protected abstract Bootstrap newClientBootstrap();
|
||||
|
||||
@Test
|
||||
public void testSpdyEcho() throws Throwable {
|
||||
for (int version = SPDY_MIN_VERSION; version <= SPDY_MAX_VERSION; version ++) {
|
||||
sb = newServerBootstrap();
|
||||
cb = newClientBootstrap();
|
||||
try {
|
||||
testSpdyEcho(version);
|
||||
} finally {
|
||||
sb.shutdown();
|
||||
cb.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void testSpdyEcho(final int version) throws Throwable {
|
||||
|
||||
ChannelBuffer frames = createFrames(version);
|
||||
|
||||
final SpdyEchoTestServerHandler sh = new SpdyEchoTestServerHandler();
|
||||
final SpdyEchoTestClientHandler ch = new SpdyEchoTestClientHandler(frames);
|
||||
|
||||
sb.childInitializer(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel channel) throws Exception {
|
||||
channel.pipeline().addLast(
|
||||
new SpdyFrameDecoder(version),
|
||||
new SpdyFrameEncoder(version),
|
||||
sh);
|
||||
}
|
||||
});
|
||||
|
||||
cb.initializer(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel channel) throws Exception {
|
||||
channel.pipeline().addLast(ch);
|
||||
}
|
||||
});
|
||||
|
||||
Channel sc = sb.localAddress(0).bind().sync().channel();
|
||||
int port = ((InetSocketAddress) sc.localAddress()).getPort();
|
||||
|
||||
Channel cc = cb.remoteAddress(SocketAddresses.LOCALHOST, port).connect().sync().channel();
|
||||
cc.write(frames);
|
||||
|
||||
while (ch.counter < frames.writerIndex() - ignoredBytes) {
|
||||
if (sh.exception.get() != null) {
|
||||
break;
|
||||
}
|
||||
if (ch.exception.get() != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
|
||||
if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
|
||||
throw sh.exception.get();
|
||||
}
|
||||
if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
|
||||
throw ch.exception.get();
|
||||
}
|
||||
if (sh.exception.get() != null) {
|
||||
throw sh.exception.get();
|
||||
}
|
||||
if (ch.exception.get() != null) {
|
||||
throw ch.exception.get();
|
||||
}
|
||||
}
|
||||
|
||||
private class SpdyEchoTestServerHandler extends ChannelInboundMessageHandlerAdapter<Object> {
|
||||
final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelInboundHandlerContext<Object> ctx, Object msg) throws Exception {
|
||||
ctx.write(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelInboundHandlerContext<Object> ctx, Throwable cause) throws Exception {
|
||||
if (exception.compareAndSet(null, cause)) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SpdyEchoTestClientHandler extends ChannelInboundStreamHandlerAdapter {
|
||||
final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
|
||||
final ChannelBuffer frames;
|
||||
volatile int counter;
|
||||
|
||||
SpdyEchoTestClientHandler(ChannelBuffer frames) {
|
||||
this.frames = frames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inboundBufferUpdated(ChannelInboundHandlerContext<Byte> ctx) throws Exception {
|
||||
ChannelBuffer m = ctx.inbound().byteBuffer();
|
||||
byte[] actual = new byte[m.readableBytes()];
|
||||
m.readBytes(actual);
|
||||
|
||||
int lastIdx = counter;
|
||||
for (int i = 0; i < actual.length; i ++) {
|
||||
assertEquals(frames.getByte(ignoredBytes + i + lastIdx), actual[i]);
|
||||
}
|
||||
|
||||
counter += actual.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelInboundHandlerContext<Byte> ctx, Throwable cause) throws Exception {
|
||||
if (exception.compareAndSet(null, cause)) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.socket.nio.NioEventLoop;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
public class NioNioSocketSpdyEchoTest extends AbstractSocketSpdyEchoTest {
|
||||
|
||||
@Override
|
||||
protected Bootstrap newClientBootstrap() {
|
||||
return new Bootstrap().eventLoop(new NioEventLoop()).channel(new NioSocketChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerBootstrap newServerBootstrap() {
|
||||
return new ServerBootstrap().eventLoop(new NioEventLoop(), new NioEventLoop()).channel(new NioServerSocketChannel());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.socket.nio.NioEventLoop;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.channel.socket.oio.OioEventLoop;
|
||||
import io.netty.channel.socket.oio.OioServerSocketChannel;
|
||||
|
||||
public class NioOioSocketSpdyEchoTest extends AbstractSocketSpdyEchoTest {
|
||||
|
||||
@Override
|
||||
protected Bootstrap newClientBootstrap() {
|
||||
return new Bootstrap().eventLoop(new NioEventLoop()).channel(new NioSocketChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerBootstrap newServerBootstrap() {
|
||||
return new ServerBootstrap().eventLoop(new OioEventLoop(), new OioEventLoop()).channel(new OioServerSocketChannel());
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.socket.nio.NioEventLoop;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.oio.OioEventLoop;
|
||||
import io.netty.channel.socket.oio.OioSocketChannel;
|
||||
|
||||
public class OioNioSocketSpdyEchoTest extends AbstractSocketSpdyEchoTest {
|
||||
|
||||
@Override
|
||||
protected Bootstrap newClientBootstrap() {
|
||||
return new Bootstrap().eventLoop(new OioEventLoop()).channel(new OioSocketChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerBootstrap newServerBootstrap() {
|
||||
return new ServerBootstrap().eventLoop(new NioEventLoop(), new NioEventLoop()).channel(new NioServerSocketChannel());
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.socket.oio.OioEventLoop;
|
||||
import io.netty.channel.socket.oio.OioServerSocketChannel;
|
||||
import io.netty.channel.socket.oio.OioSocketChannel;
|
||||
|
||||
public class OioOioSocketSpdyEchoTest extends AbstractSocketSpdyEchoTest {
|
||||
|
||||
@Override
|
||||
protected Bootstrap newClientBootstrap() {
|
||||
return new Bootstrap().eventLoop(new OioEventLoop()).channel(new OioSocketChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerBootstrap newServerBootstrap() {
|
||||
return new ServerBootstrap().eventLoop(new OioEventLoop(), new OioEventLoop()).channel(new OioServerSocketChannel());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user