Drop SPDY support (#8845)
Motivation: SPDY has been superseded by HTTP/2. Chrome has dropped support in 2016 and GFE no longer negociate it. Modifications: * drop codec * drop examples * drop constants from `ApplicationProtocolNames` Result: SPDY support dropped from Netty 5
This commit is contained in:
parent
f6f7564602
commit
ee4e46e6e2
@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.IllegalReferenceCountException;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyDataFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyDataFrame extends DefaultSpdyStreamFrame implements SpdyDataFrame {
|
||||
|
||||
private final ByteBuf data;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyDataFrame(int streamId) {
|
||||
this(streamId, Unpooled.buffer(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
* @param data the payload of the frame. Can not exceed {@link SpdyCodecUtil#SPDY_MAX_LENGTH}
|
||||
*/
|
||||
public DefaultSpdyDataFrame(int streamId, ByteBuf data) {
|
||||
super(streamId);
|
||||
if (data == null) {
|
||||
throw new NullPointerException("data");
|
||||
}
|
||||
this.data = validate(data);
|
||||
}
|
||||
|
||||
private static ByteBuf validate(ByteBuf data) {
|
||||
if (data.readableBytes() > SpdyCodecUtil.SPDY_MAX_LENGTH) {
|
||||
throw new IllegalArgumentException("data payload cannot exceed "
|
||||
+ SpdyCodecUtil.SPDY_MAX_LENGTH + " bytes");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame setStreamId(int streamId) {
|
||||
super.setStreamId(streamId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame setLast(boolean last) {
|
||||
super.setLast(last);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf content() {
|
||||
if (data.refCnt() <= 0) {
|
||||
throw new IllegalReferenceCountException(data.refCnt());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame copy() {
|
||||
return replace(content().copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame duplicate() {
|
||||
return replace(content().duplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame retainedDuplicate() {
|
||||
return replace(content().retainedDuplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame replace(ByteBuf content) {
|
||||
SpdyDataFrame frame = new DefaultSpdyDataFrame(streamId(), content);
|
||||
frame.setLast(isLast());
|
||||
return frame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return data.refCnt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame retain() {
|
||||
data.retain();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame retain(int increment) {
|
||||
data.retain(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame touch() {
|
||||
data.touch();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyDataFrame touch(Object hint) {
|
||||
data.touch(hint);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return data.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
return data.release(decrement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.append("(last: ")
|
||||
.append(isLast())
|
||||
.append(')')
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Stream-ID = ")
|
||||
.append(streamId())
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Size = ");
|
||||
if (refCnt() == 0) {
|
||||
buf.append("(freed)");
|
||||
} else {
|
||||
buf.append(content().readableBytes());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||
|
||||
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 lastGoodStreamId() {
|
||||
return lastGoodStreamId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyGoAwayFrame setLastGoodStreamId(int lastGoodStreamId) {
|
||||
checkPositiveOrZero(lastGoodStreamId, "lastGoodStreamId");
|
||||
this.lastGoodStreamId = lastGoodStreamId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySessionStatus status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyGoAwayFrame setStatus(SpdySessionStatus status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Last-good-stream-ID = ")
|
||||
.append(lastGoodStreamId())
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Status: ")
|
||||
.append(status())
|
||||
.toString();
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.CharSequenceValueConverter;
|
||||
import io.netty.handler.codec.DefaultHeaders;
|
||||
import io.netty.handler.codec.HeadersUtils;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
||||
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
|
||||
|
||||
public class DefaultSpdyHeaders extends DefaultHeaders<CharSequence, CharSequence, SpdyHeaders> implements SpdyHeaders {
|
||||
private static final NameValidator<CharSequence> SpdyNameValidator = SpdyCodecUtil::validateHeaderName;
|
||||
|
||||
public DefaultSpdyHeaders() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public DefaultSpdyHeaders(boolean validate) {
|
||||
super(CASE_INSENSITIVE_HASHER,
|
||||
validate ? HeaderValueConverterAndValidator.INSTANCE : CharSequenceValueConverter.INSTANCE,
|
||||
validate ? SpdyNameValidator : NameValidator.NOT_NULL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAsString(CharSequence name) {
|
||||
return HeadersUtils.getAsString(this, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllAsString(CharSequence name) {
|
||||
return HeadersUtils.getAllAsString(this, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<String, String>> iteratorAsString() {
|
||||
return HeadersUtils.iteratorAsString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(CharSequence name, CharSequence value) {
|
||||
return contains(name, value, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||
return contains(name, value,
|
||||
ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
|
||||
}
|
||||
|
||||
private static final class HeaderValueConverterAndValidator extends CharSequenceValueConverter {
|
||||
public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
final CharSequence seq = super.convertObject(value);
|
||||
SpdyCodecUtil.validateHeaderValue(seq);
|
||||
return seq;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyHeadersFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame
|
||||
implements SpdyHeadersFrame {
|
||||
|
||||
private boolean invalid;
|
||||
private boolean truncated;
|
||||
private final SpdyHeaders headers;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdyHeadersFrame(int streamId) {
|
||||
this(streamId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
* @param validate validate the header names and values when adding them to the {@link SpdyHeaders}
|
||||
*/
|
||||
public DefaultSpdyHeadersFrame(int streamId, boolean validate) {
|
||||
super(streamId);
|
||||
headers = new DefaultSpdyHeaders(validate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyHeadersFrame setStreamId(int streamId) {
|
||||
super.setStreamId(streamId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyHeadersFrame setLast(boolean last) {
|
||||
super.setLast(last);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid() {
|
||||
return invalid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyHeadersFrame setInvalid() {
|
||||
invalid = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTruncated() {
|
||||
return truncated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyHeadersFrame setTruncated() {
|
||||
truncated = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyHeaders headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.append("(last: ")
|
||||
.append(isLast())
|
||||
.append(')')
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Stream-ID = ")
|
||||
.append(streamId())
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Headers:")
|
||||
.append(StringUtil.NEWLINE);
|
||||
appendHeaders(buf);
|
||||
|
||||
// Remove the last newline.
|
||||
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected void appendHeaders(StringBuilder buf) {
|
||||
for (Map.Entry<CharSequence, CharSequence> e: headers()) {
|
||||
buf.append(" ");
|
||||
buf.append(e.getKey());
|
||||
buf.append(": ");
|
||||
buf.append(e.getValue());
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyPingFrame setId(int id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> ID = ")
|
||||
.append(id())
|
||||
.toString();
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 extends DefaultSpdyStreamFrame
|
||||
implements SpdyRstStreamFrame {
|
||||
|
||||
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) {
|
||||
super(streamId);
|
||||
setStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyRstStreamFrame setStreamId(int streamId) {
|
||||
super.setStreamId(streamId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyRstStreamFrame setLast(boolean last) {
|
||||
super.setLast(last);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyStreamStatus status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyRstStreamFrame setStatus(SpdyStreamStatus status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Stream-ID = ")
|
||||
.append(streamId())
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Status: ")
|
||||
.append(status())
|
||||
.toString();
|
||||
}
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
/*
|
||||
* 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<>();
|
||||
|
||||
@Override
|
||||
public Set<Integer> ids() {
|
||||
return settingsMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSet(int id) {
|
||||
return settingsMap.containsKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue(int id) {
|
||||
final Setting setting = settingsMap.get(id);
|
||||
return setting != null ? setting.getValue() : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySettingsFrame setValue(int id, int value) {
|
||||
return setValue(id, value, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySettingsFrame 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);
|
||||
}
|
||||
final Integer key = Integer.valueOf(id);
|
||||
final Setting setting = settingsMap.get(key);
|
||||
if (setting != null) {
|
||||
setting.setValue(value);
|
||||
setting.setPersist(persistValue);
|
||||
setting.setPersisted(persisted);
|
||||
} else {
|
||||
settingsMap.put(key, new Setting(value, persistValue, persisted));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySettingsFrame removeValue(int id) {
|
||||
settingsMap.remove(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistValue(int id) {
|
||||
final Setting setting = settingsMap.get(id);
|
||||
return setting != null && setting.isPersist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySettingsFrame setPersistValue(int id, boolean persistValue) {
|
||||
final Setting setting = settingsMap.get(id);
|
||||
if (setting != null) {
|
||||
setting.setPersist(persistValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersisted(int id) {
|
||||
final Setting setting = settingsMap.get(id);
|
||||
return setting != null && setting.isPersisted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySettingsFrame setPersisted(int id, boolean persisted) {
|
||||
final Setting setting = settingsMap.get(id);
|
||||
if (setting != null) {
|
||||
setting.setPersisted(persisted);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clearPreviouslyPersistedSettings() {
|
||||
return clear;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySettingsFrame setClearPreviouslyPersistedSettings(boolean clear) {
|
||||
this.clear = clear;
|
||||
return this;
|
||||
}
|
||||
|
||||
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());
|
||||
buf.append(':');
|
||||
buf.append(setting.getValue());
|
||||
buf.append(" (persist value: ");
|
||||
buf.append(setting.isPersist());
|
||||
buf.append("; persisted: ");
|
||||
buf.append(setting.isPersisted());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.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;
|
||||
|
||||
Setting(int value, boolean persist, boolean persisted) {
|
||||
this.value = value;
|
||||
this.persist = persist;
|
||||
this.persisted = persisted;
|
||||
}
|
||||
|
||||
int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
boolean isPersist() {
|
||||
return persist;
|
||||
}
|
||||
|
||||
void setPersist(boolean persist) {
|
||||
this.persist = persist;
|
||||
}
|
||||
|
||||
boolean isPersisted() {
|
||||
return persisted;
|
||||
}
|
||||
|
||||
void setPersisted(boolean persisted) {
|
||||
this.persisted = persisted;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.util.internal.ObjectUtil.checkPositive;
|
||||
|
||||
/**
|
||||
* The default {@link SpdyStreamFrame} implementation.
|
||||
*/
|
||||
public abstract class DefaultSpdyStreamFrame implements SpdyStreamFrame {
|
||||
|
||||
private int streamId;
|
||||
private boolean last;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
*/
|
||||
protected DefaultSpdyStreamFrame(int streamId) {
|
||||
setStreamId(streamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int streamId() {
|
||||
return streamId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyStreamFrame setStreamId(int streamId) {
|
||||
checkPositive(streamId, "streamId");
|
||||
this.streamId = streamId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyStreamFrame setLast(boolean last) {
|
||||
this.last = last;
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 DefaultSpdyHeadersFrame implements SpdySynReplyFrame {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
*/
|
||||
public DefaultSpdySynReplyFrame(int streamId) {
|
||||
super(streamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
* @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders}
|
||||
*/
|
||||
public DefaultSpdySynReplyFrame(int streamId, boolean validateHeaders) {
|
||||
super(streamId, validateHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynReplyFrame setStreamId(int streamId) {
|
||||
super.setStreamId(streamId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynReplyFrame setLast(boolean last) {
|
||||
super.setLast(last);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynReplyFrame setInvalid() {
|
||||
super.setInvalid();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.append("(last: ")
|
||||
.append(isLast())
|
||||
.append(')')
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Stream-ID = ")
|
||||
.append(streamId())
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Headers:")
|
||||
.append(StringUtil.NEWLINE);
|
||||
appendHeaders(buf);
|
||||
|
||||
// Remove the last newline.
|
||||
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* The default {@link SpdySynStreamFrame} implementation.
|
||||
*/
|
||||
public class DefaultSpdySynStreamFrame extends DefaultSpdyHeadersFrame
|
||||
implements SpdySynStreamFrame {
|
||||
|
||||
private int associatedStreamId;
|
||||
private byte priority;
|
||||
private boolean unidirectional;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
* @param associatedStreamId the Associated-To-Stream-ID of this frame
|
||||
* @param priority the priority of the stream
|
||||
*/
|
||||
public DefaultSpdySynStreamFrame(int streamId, int associatedStreamId, byte priority) {
|
||||
this(streamId, associatedStreamId, priority, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param streamId the Stream-ID of this frame
|
||||
* @param associatedStreamId the Associated-To-Stream-ID of this frame
|
||||
* @param priority the priority of the stream
|
||||
* @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders}
|
||||
*/
|
||||
public DefaultSpdySynStreamFrame(int streamId, int associatedStreamId, byte priority, boolean validateHeaders) {
|
||||
super(streamId, validateHeaders);
|
||||
setAssociatedStreamId(associatedStreamId);
|
||||
setPriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynStreamFrame setStreamId(int streamId) {
|
||||
super.setStreamId(streamId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynStreamFrame setLast(boolean last) {
|
||||
super.setLast(last);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynStreamFrame setInvalid() {
|
||||
super.setInvalid();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int associatedStreamId() {
|
||||
return associatedStreamId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynStreamFrame setAssociatedStreamId(int associatedStreamId) {
|
||||
checkPositiveOrZero(associatedStreamId, "associatedStreamId");
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte priority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynStreamFrame setPriority(byte priority) {
|
||||
if (priority < 0 || priority > 7) {
|
||||
throw new IllegalArgumentException(
|
||||
"Priority must be between 0 and 7 inclusive: " + priority);
|
||||
}
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnidirectional() {
|
||||
return unidirectional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdySynStreamFrame setUnidirectional(boolean unidirectional) {
|
||||
this.unidirectional = unidirectional;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.append("(last: ")
|
||||
.append(isLast())
|
||||
.append("; unidirectional: ")
|
||||
.append(isUnidirectional())
|
||||
.append(')')
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Stream-ID = ")
|
||||
.append(streamId())
|
||||
.append(StringUtil.NEWLINE);
|
||||
if (associatedStreamId != 0) {
|
||||
buf.append("--> Associated-To-Stream-ID = ")
|
||||
.append(associatedStreamId())
|
||||
.append(StringUtil.NEWLINE);
|
||||
}
|
||||
buf.append("--> Priority = ")
|
||||
.append(priority())
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Headers:")
|
||||
.append(StringUtil.NEWLINE);
|
||||
appendHeaders(buf);
|
||||
|
||||
// Remove the last newline.
|
||||
buf.setLength(buf.length() - StringUtil.NEWLINE.length());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.util.internal.ObjectUtil.checkPositive;
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||
|
||||
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 streamId() {
|
||||
return streamId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyWindowUpdateFrame setStreamId(int streamId) {
|
||||
checkPositiveOrZero(streamId, "streamId");
|
||||
this.streamId = streamId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deltaWindowSize() {
|
||||
return deltaWindowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyWindowUpdateFrame setDeltaWindowSize(int deltaWindowSize) {
|
||||
checkPositive(deltaWindowSize, "deltaWindowSize");
|
||||
this.deltaWindowSize = deltaWindowSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
.append(StringUtil.simpleClassName(this))
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Stream-ID = ")
|
||||
.append(streamId())
|
||||
.append(StringUtil.NEWLINE)
|
||||
.append("--> Delta-Window-Size = ")
|
||||
.append(deltaWindowSize())
|
||||
.toString();
|
||||
}
|
||||
}
|
@ -1,333 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ByteBuf;
|
||||
|
||||
final class SpdyCodecUtil {
|
||||
|
||||
static final int SPDY_SESSION_STREAM_ID = 0;
|
||||
|
||||
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_DATA_FRAME = 0;
|
||||
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_PUSH_PROMISE_FRAME = 5;
|
||||
static final int SPDY_PING_FRAME = 6;
|
||||
static final int SPDY_GOAWAY_FRAME = 7;
|
||||
static final int SPDY_HEADERS_FRAME = 8;
|
||||
static final int SPDY_WINDOW_UPDATE_FRAME = 9;
|
||||
|
||||
static final byte SPDY_FLAG_FIN = 0x01;
|
||||
static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02;
|
||||
|
||||
static final byte SPDY_SETTINGS_CLEAR = 0x01;
|
||||
static final byte SPDY_SETTINGS_PERSIST_VALUE = 0x01;
|
||||
static final byte SPDY_SETTINGS_PERSISTED = 0x02;
|
||||
|
||||
static final int SPDY_SETTINGS_MAX_ID = 0xFFFFFF; // ID is a 24-bit field
|
||||
|
||||
static final int SPDY_MAX_NV_LENGTH = 0xFFFF; // Length is a 16-bit field
|
||||
|
||||
// Zlib Dictionary
|
||||
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 SpdyCodecUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian unsigned short integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedShort(ByteBuf buf, int offset) {
|
||||
return (buf.getByte(offset) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 1) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian unsigned medium integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedMedium(ByteBuf buf, int offset) {
|
||||
return (buf.getByte(offset) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 2) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian (31-bit) integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedInt(ByteBuf buf, int offset) {
|
||||
return (buf.getByte(offset) & 0x7F) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 3) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian signed integer from the buffer.
|
||||
*/
|
||||
static int getSignedInt(ByteBuf buf, int offset) {
|
||||
return (buf.getByte(offset) & 0xFF) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 3) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(CharSequence 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 >= 'A' && c <= 'Z') {
|
||||
throw new IllegalArgumentException("name must be all lower case.");
|
||||
}
|
||||
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(CharSequence value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
for (int i = 0; i < value.length(); i ++) {
|
||||
char c = value.charAt(i);
|
||||
if (c == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"value contains null character: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ByteBuf;
|
||||
import io.netty.buffer.ByteBufHolder;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
/**
|
||||
* A SPDY Protocol DATA Frame
|
||||
*/
|
||||
public interface SpdyDataFrame extends ByteBufHolder, SpdyStreamFrame {
|
||||
|
||||
@Override
|
||||
SpdyDataFrame setStreamId(int streamID);
|
||||
|
||||
@Override
|
||||
SpdyDataFrame setLast(boolean last);
|
||||
|
||||
/**
|
||||
* Returns the data payload of this frame. If there is no data payload
|
||||
* {@link Unpooled#EMPTY_BUFFER} is returned.
|
||||
*
|
||||
* The data payload cannot exceed 16777215 bytes.
|
||||
*/
|
||||
@Override
|
||||
ByteBuf content();
|
||||
|
||||
@Override
|
||||
SpdyDataFrame copy();
|
||||
|
||||
@Override
|
||||
SpdyDataFrame duplicate();
|
||||
|
||||
@Override
|
||||
SpdyDataFrame retainedDuplicate();
|
||||
|
||||
@Override
|
||||
SpdyDataFrame replace(ByteBuf content);
|
||||
|
||||
@Override
|
||||
SpdyDataFrame retain();
|
||||
|
||||
@Override
|
||||
SpdyDataFrame retain(int increment);
|
||||
|
||||
@Override
|
||||
SpdyDataFrame touch();
|
||||
|
||||
@Override
|
||||
SpdyDataFrame touch(Object hint);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdyFrame {
|
||||
// Tag interface
|
||||
}
|
@ -1,412 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundHandler;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link ChannelHandler} that encodes and decodes SPDY Frames.
|
||||
*/
|
||||
public class SpdyFrameCodec extends ByteToMessageDecoder
|
||||
implements SpdyFrameDecoderDelegate, ChannelOutboundHandler {
|
||||
|
||||
private static final SpdyProtocolException INVALID_FRAME =
|
||||
new SpdyProtocolException("Received invalid frame");
|
||||
|
||||
private final SpdyFrameDecoder spdyFrameDecoder;
|
||||
private final SpdyFrameEncoder spdyFrameEncoder;
|
||||
private final SpdyHeaderBlockDecoder spdyHeaderBlockDecoder;
|
||||
private final SpdyHeaderBlockEncoder spdyHeaderBlockEncoder;
|
||||
|
||||
private SpdyHeadersFrame spdyHeadersFrame;
|
||||
private SpdySettingsFrame spdySettingsFrame;
|
||||
|
||||
private ChannelHandlerContext ctx;
|
||||
private boolean read;
|
||||
private final boolean validateHeaders;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version},
|
||||
* {@code validateHeaders (true)}, 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(SpdyVersion version) {
|
||||
this(version, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version},
|
||||
* {@code validateHeaders}, 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(SpdyVersion version, boolean validateHeaders) {
|
||||
this(version, 8192, 16384, 6, 15, 8, validateHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version}, {@code validateHeaders (true)},
|
||||
* decoder and encoder options.
|
||||
*/
|
||||
public SpdyFrameCodec(
|
||||
SpdyVersion version, int maxChunkSize, int maxHeaderSize,
|
||||
int compressionLevel, int windowBits, int memLevel) {
|
||||
this(version, maxChunkSize, maxHeaderSize, compressionLevel, windowBits, memLevel, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version}, {@code validateHeaders},
|
||||
* decoder and encoder options.
|
||||
*/
|
||||
public SpdyFrameCodec(
|
||||
SpdyVersion version, int maxChunkSize, int maxHeaderSize,
|
||||
int compressionLevel, int windowBits, int memLevel, boolean validateHeaders) {
|
||||
this(version, maxChunkSize,
|
||||
SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize),
|
||||
SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel), validateHeaders);
|
||||
}
|
||||
|
||||
protected SpdyFrameCodec(SpdyVersion version, int maxChunkSize,
|
||||
SpdyHeaderBlockDecoder spdyHeaderBlockDecoder, SpdyHeaderBlockEncoder spdyHeaderBlockEncoder,
|
||||
boolean validateHeaders) {
|
||||
spdyFrameDecoder = new SpdyFrameDecoder(version, this, maxChunkSize);
|
||||
spdyFrameEncoder = new SpdyFrameEncoder(version);
|
||||
this.spdyHeaderBlockDecoder = spdyHeaderBlockDecoder;
|
||||
this.spdyHeaderBlockEncoder = spdyHeaderBlockEncoder;
|
||||
this.validateHeaders = validateHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||
super.handlerAdded(ctx);
|
||||
this.ctx = ctx;
|
||||
ctx.channel().closeFuture().addListener((ChannelFutureListener) future -> {
|
||||
spdyHeaderBlockDecoder.end();
|
||||
spdyHeaderBlockEncoder.end();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
spdyFrameDecoder.decode(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||
if (!read) {
|
||||
if (!ctx.channel().config().isAutoRead()) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
read = false;
|
||||
super.channelReadComplete(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
|
||||
ctx.bind(localAddress, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
|
||||
ChannelPromise promise) throws Exception {
|
||||
ctx.connect(remoteAddress, localAddress, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||
ctx.disconnect(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||
ctx.close(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||
ctx.register(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||
ctx.deregister(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
ByteBuf frame;
|
||||
|
||||
if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
frame = spdyFrameEncoder.encodeDataFrame(
|
||||
ctx.alloc(),
|
||||
spdyDataFrame.streamId(),
|
||||
spdyDataFrame.isLast(),
|
||||
spdyDataFrame.content()
|
||||
);
|
||||
spdyDataFrame.release();
|
||||
ctx.write(frame, promise);
|
||||
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdySynStreamFrame);
|
||||
try {
|
||||
frame = spdyFrameEncoder.encodeSynStreamFrame(
|
||||
ctx.alloc(),
|
||||
spdySynStreamFrame.streamId(),
|
||||
spdySynStreamFrame.associatedStreamId(),
|
||||
spdySynStreamFrame.priority(),
|
||||
spdySynStreamFrame.isLast(),
|
||||
spdySynStreamFrame.isUnidirectional(),
|
||||
headerBlock
|
||||
);
|
||||
} finally {
|
||||
headerBlock.release();
|
||||
}
|
||||
ctx.write(frame, promise);
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdySynReplyFrame);
|
||||
try {
|
||||
frame = spdyFrameEncoder.encodeSynReplyFrame(
|
||||
ctx.alloc(),
|
||||
spdySynReplyFrame.streamId(),
|
||||
spdySynReplyFrame.isLast(),
|
||||
headerBlock
|
||||
);
|
||||
} finally {
|
||||
headerBlock.release();
|
||||
}
|
||||
ctx.write(frame, promise);
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
frame = spdyFrameEncoder.encodeRstStreamFrame(
|
||||
ctx.alloc(),
|
||||
spdyRstStreamFrame.streamId(),
|
||||
spdyRstStreamFrame.status().code()
|
||||
);
|
||||
ctx.write(frame, promise);
|
||||
|
||||
} else if (msg instanceof SpdySettingsFrame) {
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
frame = spdyFrameEncoder.encodeSettingsFrame(
|
||||
ctx.alloc(),
|
||||
spdySettingsFrame
|
||||
);
|
||||
ctx.write(frame, promise);
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
frame = spdyFrameEncoder.encodePingFrame(
|
||||
ctx.alloc(),
|
||||
spdyPingFrame.id()
|
||||
);
|
||||
ctx.write(frame, promise);
|
||||
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
|
||||
frame = spdyFrameEncoder.encodeGoAwayFrame(
|
||||
ctx.alloc(),
|
||||
spdyGoAwayFrame.lastGoodStreamId(),
|
||||
spdyGoAwayFrame.status().code()
|
||||
);
|
||||
ctx.write(frame, promise);
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(ctx.alloc(), spdyHeadersFrame);
|
||||
try {
|
||||
frame = spdyFrameEncoder.encodeHeadersFrame(
|
||||
ctx.alloc(),
|
||||
spdyHeadersFrame.streamId(),
|
||||
spdyHeadersFrame.isLast(),
|
||||
headerBlock
|
||||
);
|
||||
} finally {
|
||||
headerBlock.release();
|
||||
}
|
||||
ctx.write(frame, promise);
|
||||
|
||||
} else if (msg instanceof SpdyWindowUpdateFrame) {
|
||||
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
|
||||
frame = spdyFrameEncoder.encodeWindowUpdateFrame(
|
||||
ctx.alloc(),
|
||||
spdyWindowUpdateFrame.streamId(),
|
||||
spdyWindowUpdateFrame.deltaWindowSize()
|
||||
);
|
||||
ctx.write(frame, promise);
|
||||
} else {
|
||||
throw new UnsupportedMessageTypeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readDataFrame(int streamId, boolean last, ByteBuf data) {
|
||||
read = true;
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
|
||||
spdyDataFrame.setLast(last);
|
||||
ctx.fireChannelRead(spdyDataFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSynStreamFrame(
|
||||
int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional) {
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders);
|
||||
spdySynStreamFrame.setLast(last);
|
||||
spdySynStreamFrame.setUnidirectional(unidirectional);
|
||||
spdyHeadersFrame = spdySynStreamFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSynReplyFrame(int streamId, boolean last) {
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders);
|
||||
spdySynReplyFrame.setLast(last);
|
||||
spdyHeadersFrame = spdySynReplyFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readRstStreamFrame(int streamId, int statusCode) {
|
||||
read = true;
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, statusCode);
|
||||
ctx.fireChannelRead(spdyRstStreamFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettingsFrame(boolean clearPersisted) {
|
||||
read = true;
|
||||
|
||||
spdySettingsFrame = new DefaultSpdySettingsFrame();
|
||||
spdySettingsFrame.setClearPreviouslyPersistedSettings(clearPersisted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSetting(int id, int value, boolean persistValue, boolean persisted) {
|
||||
spdySettingsFrame.setValue(id, value, persistValue, persisted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettingsEnd() {
|
||||
read = true;
|
||||
|
||||
Object frame = spdySettingsFrame;
|
||||
spdySettingsFrame = null;
|
||||
ctx.fireChannelRead(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPingFrame(int id) {
|
||||
read = true;
|
||||
|
||||
SpdyPingFrame spdyPingFrame = new DefaultSpdyPingFrame(id);
|
||||
ctx.fireChannelRead(spdyPingFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readGoAwayFrame(int lastGoodStreamId, int statusCode) {
|
||||
read = true;
|
||||
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamId, statusCode);
|
||||
ctx.fireChannelRead(spdyGoAwayFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readHeadersFrame(int streamId, boolean last) {
|
||||
spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders);
|
||||
spdyHeadersFrame.setLast(last);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readWindowUpdateFrame(int streamId, int deltaWindowSize) {
|
||||
read = true;
|
||||
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame = new DefaultSpdyWindowUpdateFrame(streamId, deltaWindowSize);
|
||||
ctx.fireChannelRead(spdyWindowUpdateFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readHeaderBlock(ByteBuf headerBlock) {
|
||||
try {
|
||||
spdyHeaderBlockDecoder.decode(ctx.alloc(), headerBlock, spdyHeadersFrame);
|
||||
} catch (Exception e) {
|
||||
ctx.fireExceptionCaught(e);
|
||||
} finally {
|
||||
headerBlock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readHeaderBlockEnd() {
|
||||
Object frame = null;
|
||||
try {
|
||||
spdyHeaderBlockDecoder.endHeaderBlock(spdyHeadersFrame);
|
||||
frame = spdyHeadersFrame;
|
||||
spdyHeadersFrame = null;
|
||||
} catch (Exception e) {
|
||||
ctx.fireExceptionCaught(e);
|
||||
}
|
||||
if (frame != null) {
|
||||
read = true;
|
||||
|
||||
ctx.fireChannelRead(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrameError(String message) {
|
||||
ctx.fireExceptionCaught(INVALID_FRAME);
|
||||
}
|
||||
}
|
@ -1,464 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_DATA_FLAG_FIN;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_DATA_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_FLAG_FIN;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_FLAG_UNIDIRECTIONAL;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_GOAWAY_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_HEADERS_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_HEADER_FLAGS_OFFSET;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_HEADER_LENGTH_OFFSET;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_HEADER_SIZE;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_HEADER_TYPE_OFFSET;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_PING_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_RST_STREAM_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SETTINGS_CLEAR;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SETTINGS_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SETTINGS_PERSISTED;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SETTINGS_PERSIST_VALUE;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SYN_REPLY_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SYN_STREAM_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_WINDOW_UPDATE_FRAME;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.getSignedInt;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedInt;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedMedium;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedShort;
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositive;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
/**
|
||||
* Decodes {@link ByteBuf}s into SPDY Frames.
|
||||
*/
|
||||
public class SpdyFrameDecoder {
|
||||
|
||||
private final int spdyVersion;
|
||||
private final int maxChunkSize;
|
||||
|
||||
private final SpdyFrameDecoderDelegate delegate;
|
||||
|
||||
private State state;
|
||||
|
||||
// SPDY common header fields
|
||||
private byte flags;
|
||||
private int length;
|
||||
private int streamId;
|
||||
|
||||
private int numSettings;
|
||||
|
||||
private enum State {
|
||||
READ_COMMON_HEADER,
|
||||
READ_DATA_FRAME,
|
||||
READ_SYN_STREAM_FRAME,
|
||||
READ_SYN_REPLY_FRAME,
|
||||
READ_RST_STREAM_FRAME,
|
||||
READ_SETTINGS_FRAME,
|
||||
READ_SETTING,
|
||||
READ_PING_FRAME,
|
||||
READ_GOAWAY_FRAME,
|
||||
READ_HEADERS_FRAME,
|
||||
READ_WINDOW_UPDATE_FRAME,
|
||||
READ_HEADER_BLOCK,
|
||||
DISCARD_FRAME,
|
||||
FRAME_ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version}
|
||||
* and the default {@code maxChunkSize (8192)}.
|
||||
*/
|
||||
public SpdyFrameDecoder(SpdyVersion spdyVersion, SpdyFrameDecoderDelegate delegate) {
|
||||
this(spdyVersion, delegate, 8192);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public SpdyFrameDecoder(SpdyVersion spdyVersion, SpdyFrameDecoderDelegate delegate, int maxChunkSize) {
|
||||
if (spdyVersion == null) {
|
||||
throw new NullPointerException("spdyVersion");
|
||||
}
|
||||
if (delegate == null) {
|
||||
throw new NullPointerException("delegate");
|
||||
}
|
||||
checkPositive(maxChunkSize, "maxChunkSize");
|
||||
this.spdyVersion = spdyVersion.getVersion();
|
||||
this.delegate = delegate;
|
||||
this.maxChunkSize = maxChunkSize;
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
public void decode(ByteBuf buffer) {
|
||||
boolean last;
|
||||
int statusCode;
|
||||
|
||||
while (true) {
|
||||
switch(state) {
|
||||
case READ_COMMON_HEADER:
|
||||
if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
int frameOffset = buffer.readerIndex();
|
||||
int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
|
||||
int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
|
||||
buffer.skipBytes(SPDY_HEADER_SIZE);
|
||||
|
||||
boolean control = (buffer.getByte(frameOffset) & 0x80) != 0;
|
||||
|
||||
int version;
|
||||
int type;
|
||||
if (control) {
|
||||
// Decode control frame common header
|
||||
version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
|
||||
type = getUnsignedShort(buffer, frameOffset + SPDY_HEADER_TYPE_OFFSET);
|
||||
streamId = 0; // Default to session Stream-ID
|
||||
} else {
|
||||
// Decode data frame common header
|
||||
version = spdyVersion; // Default to expected version
|
||||
type = SPDY_DATA_FRAME;
|
||||
streamId = getUnsignedInt(buffer, frameOffset);
|
||||
}
|
||||
|
||||
flags = buffer.getByte(flagsOffset);
|
||||
length = getUnsignedMedium(buffer, lengthOffset);
|
||||
|
||||
// Check version first then validity
|
||||
if (version != spdyVersion) {
|
||||
state = State.FRAME_ERROR;
|
||||
delegate.readFrameError("Invalid SPDY Version");
|
||||
} else if (!isValidFrameHeader(streamId, type, flags, length)) {
|
||||
state = State.FRAME_ERROR;
|
||||
delegate.readFrameError("Invalid Frame Error");
|
||||
} else {
|
||||
state = getNextState(type, length);
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_DATA_FRAME:
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
delegate.readDataFrame(streamId, hasFlag(flags, SPDY_DATA_FLAG_FIN), Unpooled.buffer(0));
|
||||
break;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
ByteBuf data = buffer.alloc().buffer(dataLength);
|
||||
data.writeBytes(buffer, dataLength);
|
||||
length -= dataLength;
|
||||
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
last = length == 0 && hasFlag(flags, SPDY_DATA_FLAG_FIN);
|
||||
|
||||
delegate.readDataFrame(streamId, last, data);
|
||||
break;
|
||||
|
||||
case READ_SYN_STREAM_FRAME:
|
||||
if (buffer.readableBytes() < 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = buffer.readerIndex();
|
||||
streamId = getUnsignedInt(buffer, offset);
|
||||
int associatedToStreamId = getUnsignedInt(buffer, offset + 4);
|
||||
byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
|
||||
last = hasFlag(flags, SPDY_FLAG_FIN);
|
||||
boolean unidirectional = hasFlag(flags, SPDY_FLAG_UNIDIRECTIONAL);
|
||||
buffer.skipBytes(10);
|
||||
length -= 10;
|
||||
|
||||
if (streamId == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
delegate.readFrameError("Invalid SYN_STREAM Frame");
|
||||
} else {
|
||||
state = State.READ_HEADER_BLOCK;
|
||||
delegate.readSynStreamFrame(streamId, associatedToStreamId, priority, last, unidirectional);
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_SYN_REPLY_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
streamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
last = hasFlag(flags, SPDY_FLAG_FIN);
|
||||
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
if (streamId == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
delegate.readFrameError("Invalid SYN_REPLY Frame");
|
||||
} else {
|
||||
state = State.READ_HEADER_BLOCK;
|
||||
delegate.readSynReplyFrame(streamId, last);
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_RST_STREAM_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
streamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
if (streamId == 0 || statusCode == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
delegate.readFrameError("Invalid RST_STREAM Frame");
|
||||
} else {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
delegate.readRstStreamFrame(streamId, statusCode);
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_SETTINGS_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean clear = hasFlag(flags, SPDY_SETTINGS_CLEAR);
|
||||
|
||||
numSettings = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
// Validate frame length against number of entries. Each ID/Value entry is 8 bytes.
|
||||
if ((length & 0x07) != 0 || length >> 3 != numSettings) {
|
||||
state = State.FRAME_ERROR;
|
||||
delegate.readFrameError("Invalid SETTINGS Frame");
|
||||
} else {
|
||||
state = State.READ_SETTING;
|
||||
delegate.readSettingsFrame(clear);
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_SETTING:
|
||||
if (numSettings == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
delegate.readSettingsEnd();
|
||||
break;
|
||||
}
|
||||
|
||||
if (buffer.readableBytes() < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte settingsFlags = buffer.getByte(buffer.readerIndex());
|
||||
int id = getUnsignedMedium(buffer, buffer.readerIndex() + 1);
|
||||
int value = getSignedInt(buffer, buffer.readerIndex() + 4);
|
||||
boolean persistValue = hasFlag(settingsFlags, SPDY_SETTINGS_PERSIST_VALUE);
|
||||
boolean persisted = hasFlag(settingsFlags, SPDY_SETTINGS_PERSISTED);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
--numSettings;
|
||||
|
||||
delegate.readSetting(id, value, persistValue, persisted);
|
||||
break;
|
||||
|
||||
case READ_PING_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
int pingId = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
state = State.READ_COMMON_HEADER;
|
||||
delegate.readPingFrame(pingId);
|
||||
break;
|
||||
|
||||
case READ_GOAWAY_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
int lastGoodStreamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
state = State.READ_COMMON_HEADER;
|
||||
delegate.readGoAwayFrame(lastGoodStreamId, statusCode);
|
||||
break;
|
||||
|
||||
case READ_HEADERS_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
streamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
last = hasFlag(flags, SPDY_FLAG_FIN);
|
||||
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
if (streamId == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
delegate.readFrameError("Invalid HEADERS Frame");
|
||||
} else {
|
||||
state = State.READ_HEADER_BLOCK;
|
||||
delegate.readHeadersFrame(streamId, last);
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_WINDOW_UPDATE_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
streamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
int deltaWindowSize = getUnsignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
if (deltaWindowSize == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
delegate.readFrameError("Invalid WINDOW_UPDATE Frame");
|
||||
} else {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
delegate.readWindowUpdateFrame(streamId, deltaWindowSize);
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_HEADER_BLOCK:
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
delegate.readHeaderBlockEnd();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!buffer.isReadable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int compressedBytes = Math.min(buffer.readableBytes(), length);
|
||||
ByteBuf headerBlock = buffer.alloc().buffer(compressedBytes);
|
||||
headerBlock.writeBytes(buffer, compressedBytes);
|
||||
length -= compressedBytes;
|
||||
|
||||
delegate.readHeaderBlock(headerBlock);
|
||||
break;
|
||||
|
||||
case DISCARD_FRAME:
|
||||
int numBytes = Math.min(buffer.readableBytes(), length);
|
||||
buffer.skipBytes(numBytes);
|
||||
length -= numBytes;
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
case FRAME_ERROR:
|
||||
buffer.skipBytes(buffer.readableBytes());
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasFlag(byte flags, byte flag) {
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
|
||||
private static State getNextState(int type, int length) {
|
||||
switch (type) {
|
||||
case SPDY_DATA_FRAME:
|
||||
return State.READ_DATA_FRAME;
|
||||
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
return State.READ_SYN_STREAM_FRAME;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
return State.READ_SYN_REPLY_FRAME;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
return State.READ_RST_STREAM_FRAME;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
return State.READ_SETTINGS_FRAME;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
return State.READ_PING_FRAME;
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
return State.READ_GOAWAY_FRAME;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
return State.READ_HEADERS_FRAME;
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return State.READ_WINDOW_UPDATE_FRAME;
|
||||
|
||||
default:
|
||||
if (length != 0) {
|
||||
return State.DISCARD_FRAME;
|
||||
} else {
|
||||
return State.READ_COMMON_HEADER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidFrameHeader(int streamId, int type, byte flags, int length) {
|
||||
switch (type) {
|
||||
case SPDY_DATA_FRAME:
|
||||
return streamId != 0;
|
||||
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
return length >= 10;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
return length >= 4;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
return flags == 0 && length == 8;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
return length >= 4;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
return length == 4;
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
return length == 8;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
return length >= 4;
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return length == 8;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Callback interface for {@link SpdyFrameDecoder}.
|
||||
*/
|
||||
public interface SpdyFrameDecoderDelegate {
|
||||
|
||||
/**
|
||||
* Called when a DATA frame is received.
|
||||
*/
|
||||
void readDataFrame(int streamId, boolean last, ByteBuf data);
|
||||
|
||||
/**
|
||||
* Called when a SYN_STREAM frame is received.
|
||||
* The Name/Value Header Block is not included. See readHeaderBlock().
|
||||
*/
|
||||
void readSynStreamFrame(
|
||||
int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional);
|
||||
|
||||
/**
|
||||
* Called when a SYN_REPLY frame is received.
|
||||
* The Name/Value Header Block is not included. See readHeaderBlock().
|
||||
*/
|
||||
void readSynReplyFrame(int streamId, boolean last);
|
||||
|
||||
/**
|
||||
* Called when a RST_STREAM frame is received.
|
||||
*/
|
||||
void readRstStreamFrame(int streamId, int statusCode);
|
||||
|
||||
/**
|
||||
* Called when a SETTINGS frame is received.
|
||||
* Settings are not included. See readSetting().
|
||||
*/
|
||||
void readSettingsFrame(boolean clearPersisted);
|
||||
|
||||
/**
|
||||
* Called when an individual setting within a SETTINGS frame is received.
|
||||
*/
|
||||
void readSetting(int id, int value, boolean persistValue, boolean persisted);
|
||||
|
||||
/**
|
||||
* Called when the entire SETTINGS frame has been received.
|
||||
*/
|
||||
void readSettingsEnd();
|
||||
|
||||
/**
|
||||
* Called when a PING frame is received.
|
||||
*/
|
||||
void readPingFrame(int id);
|
||||
|
||||
/**
|
||||
* Called when a GOAWAY frame is received.
|
||||
*/
|
||||
void readGoAwayFrame(int lastGoodStreamId, int statusCode);
|
||||
|
||||
/**
|
||||
* Called when a HEADERS frame is received.
|
||||
* The Name/Value Header Block is not included. See readHeaderBlock().
|
||||
*/
|
||||
void readHeadersFrame(int streamId, boolean last);
|
||||
|
||||
/**
|
||||
* Called when a WINDOW_UPDATE frame is received.
|
||||
*/
|
||||
void readWindowUpdateFrame(int streamId, int deltaWindowSize);
|
||||
|
||||
/**
|
||||
* Called when the header block within a SYN_STREAM, SYN_REPLY, or HEADERS frame is received.
|
||||
*/
|
||||
void readHeaderBlock(ByteBuf headerBlock);
|
||||
|
||||
/**
|
||||
* Called when an entire header block has been received.
|
||||
*/
|
||||
void readHeaderBlockEnd();
|
||||
|
||||
/**
|
||||
* Called when an unrecoverable session error has occurred.
|
||||
*/
|
||||
void readFrameError(String message);
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
/**
|
||||
* Encodes a SPDY Frame into a {@link ByteBuf}.
|
||||
*/
|
||||
public class SpdyFrameEncoder {
|
||||
|
||||
private final int version;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code spdyVersion}.
|
||||
*/
|
||||
public SpdyFrameEncoder(SpdyVersion spdyVersion) {
|
||||
if (spdyVersion == null) {
|
||||
throw new NullPointerException("spdyVersion");
|
||||
}
|
||||
version = spdyVersion.getVersion();
|
||||
}
|
||||
|
||||
private void writeControlFrameHeader(ByteBuf buffer, int type, byte flags, int length) {
|
||||
buffer.writeShort(version | 0x8000);
|
||||
buffer.writeShort(type);
|
||||
buffer.writeByte(flags);
|
||||
buffer.writeMedium(length);
|
||||
}
|
||||
|
||||
public ByteBuf encodeDataFrame(ByteBufAllocator allocator, int streamId, boolean last, ByteBuf data) {
|
||||
byte flags = last ? SPDY_DATA_FLAG_FIN : 0;
|
||||
int length = data.readableBytes();
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
frame.writeInt(streamId & 0x7FFFFFFF);
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(length);
|
||||
frame.writeBytes(data, data.readerIndex(), length);
|
||||
return frame;
|
||||
}
|
||||
|
||||
public ByteBuf encodeSynStreamFrame(ByteBufAllocator allocator, int streamId, int associatedToStreamId,
|
||||
byte priority, boolean last, boolean unidirectional, ByteBuf headerBlock) {
|
||||
int headerBlockLength = headerBlock.readableBytes();
|
||||
byte flags = last ? SPDY_FLAG_FIN : 0;
|
||||
if (unidirectional) {
|
||||
flags |= SPDY_FLAG_UNIDIRECTIONAL;
|
||||
}
|
||||
int length = 10 + headerBlockLength;
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
writeControlFrameHeader(frame, SPDY_SYN_STREAM_FRAME, flags, length);
|
||||
frame.writeInt(streamId);
|
||||
frame.writeInt(associatedToStreamId);
|
||||
frame.writeShort((priority & 0xFF) << 13);
|
||||
frame.writeBytes(headerBlock, headerBlock.readerIndex(), headerBlockLength);
|
||||
return frame;
|
||||
}
|
||||
|
||||
public ByteBuf encodeSynReplyFrame(ByteBufAllocator allocator, int streamId, boolean last, ByteBuf headerBlock) {
|
||||
int headerBlockLength = headerBlock.readableBytes();
|
||||
byte flags = last ? SPDY_FLAG_FIN : 0;
|
||||
int length = 4 + headerBlockLength;
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
writeControlFrameHeader(frame, SPDY_SYN_REPLY_FRAME, flags, length);
|
||||
frame.writeInt(streamId);
|
||||
frame.writeBytes(headerBlock, headerBlock.readerIndex(), headerBlockLength);
|
||||
return frame;
|
||||
}
|
||||
|
||||
public ByteBuf encodeRstStreamFrame(ByteBufAllocator allocator, int streamId, int statusCode) {
|
||||
byte flags = 0;
|
||||
int length = 8;
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
writeControlFrameHeader(frame, SPDY_RST_STREAM_FRAME, flags, length);
|
||||
frame.writeInt(streamId);
|
||||
frame.writeInt(statusCode);
|
||||
return frame;
|
||||
}
|
||||
|
||||
public ByteBuf encodeSettingsFrame(ByteBufAllocator allocator, SpdySettingsFrame spdySettingsFrame) {
|
||||
Set<Integer> ids = spdySettingsFrame.ids();
|
||||
int numSettings = ids.size();
|
||||
|
||||
byte flags = spdySettingsFrame.clearPreviouslyPersistedSettings() ?
|
||||
SPDY_SETTINGS_CLEAR : 0;
|
||||
int length = 4 + 8 * numSettings;
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
writeControlFrameHeader(frame, SPDY_SETTINGS_FRAME, flags, length);
|
||||
frame.writeInt(numSettings);
|
||||
for (Integer id : ids) {
|
||||
flags = 0;
|
||||
if (spdySettingsFrame.isPersistValue(id)) {
|
||||
flags |= SPDY_SETTINGS_PERSIST_VALUE;
|
||||
}
|
||||
if (spdySettingsFrame.isPersisted(id)) {
|
||||
flags |= SPDY_SETTINGS_PERSISTED;
|
||||
}
|
||||
frame.writeByte(flags);
|
||||
frame.writeMedium(id);
|
||||
frame.writeInt(spdySettingsFrame.getValue(id));
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
public ByteBuf encodePingFrame(ByteBufAllocator allocator, int id) {
|
||||
byte flags = 0;
|
||||
int length = 4;
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
writeControlFrameHeader(frame, SPDY_PING_FRAME, flags, length);
|
||||
frame.writeInt(id);
|
||||
return frame;
|
||||
}
|
||||
|
||||
public ByteBuf encodeGoAwayFrame(ByteBufAllocator allocator, int lastGoodStreamId, int statusCode) {
|
||||
byte flags = 0;
|
||||
int length = 8;
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
writeControlFrameHeader(frame, SPDY_GOAWAY_FRAME, flags, length);
|
||||
frame.writeInt(lastGoodStreamId);
|
||||
frame.writeInt(statusCode);
|
||||
return frame;
|
||||
}
|
||||
|
||||
public ByteBuf encodeHeadersFrame(ByteBufAllocator allocator, int streamId, boolean last, ByteBuf headerBlock) {
|
||||
int headerBlockLength = headerBlock.readableBytes();
|
||||
byte flags = last ? SPDY_FLAG_FIN : 0;
|
||||
int length = 4 + headerBlockLength;
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
writeControlFrameHeader(frame, SPDY_HEADERS_FRAME, flags, length);
|
||||
frame.writeInt(streamId);
|
||||
frame.writeBytes(headerBlock, headerBlock.readerIndex(), headerBlockLength);
|
||||
return frame;
|
||||
}
|
||||
|
||||
public ByteBuf encodeWindowUpdateFrame(ByteBufAllocator allocator, int streamId, int deltaWindowSize) {
|
||||
byte flags = 0;
|
||||
int length = 8;
|
||||
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
|
||||
writeControlFrameHeader(frame, SPDY_WINDOW_UPDATE_FRAME, flags, length);
|
||||
frame.writeInt(streamId);
|
||||
frame.writeInt(deltaWindowSize);
|
||||
return frame;
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdyGoAwayFrame extends SpdyFrame {
|
||||
|
||||
/**
|
||||
* Returns the Last-good-stream-ID of this frame.
|
||||
*/
|
||||
int lastGoodStreamId();
|
||||
|
||||
/**
|
||||
* Sets the Last-good-stream-ID of this frame. The Last-good-stream-ID
|
||||
* cannot be negative.
|
||||
*/
|
||||
SpdyGoAwayFrame setLastGoodStreamId(int lastGoodStreamId);
|
||||
|
||||
/**
|
||||
* Returns the status of this frame.
|
||||
*/
|
||||
SpdySessionStatus status();
|
||||
|
||||
/**
|
||||
* Sets the status of this frame.
|
||||
*/
|
||||
SpdyGoAwayFrame setStatus(SpdySessionStatus status);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
|
||||
abstract class SpdyHeaderBlockDecoder {
|
||||
|
||||
static SpdyHeaderBlockDecoder newInstance(SpdyVersion spdyVersion, int maxHeaderSize) {
|
||||
return new SpdyHeaderBlockZlibDecoder(spdyVersion, maxHeaderSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a SPDY Header Block, adding the Name/Value pairs to the given Headers frame.
|
||||
* If the header block is malformed, the Headers frame will be marked as invalid.
|
||||
* A stream error with status code PROTOCOL_ERROR must be issued in response to an invalid frame.
|
||||
*
|
||||
* @param alloc the {@link ByteBufAllocator} which can be used to allocate new {@link ByteBuf}s
|
||||
* @param headerBlock the HeaderBlock to decode
|
||||
* @param frame the Headers frame that receives the Name/Value pairs
|
||||
* @throws Exception If the header block is malformed in a way that prevents any future
|
||||
* decoding of any other header blocks, an exception will be thrown.
|
||||
* A session error with status code PROTOCOL_ERROR must be issued.
|
||||
*/
|
||||
abstract void decode(ByteBufAllocator alloc, ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception;
|
||||
|
||||
abstract void endHeaderBlock(SpdyHeadersFrame frame) throws Exception;
|
||||
|
||||
abstract void end();
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
|
||||
abstract class SpdyHeaderBlockEncoder {
|
||||
|
||||
static SpdyHeaderBlockEncoder newInstance(
|
||||
SpdyVersion version, int compressionLevel, int windowBits, int memLevel) {
|
||||
return new SpdyHeaderBlockZlibEncoder(version, compressionLevel);
|
||||
}
|
||||
|
||||
abstract ByteBuf encode(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception;
|
||||
abstract void end();
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 com.jcraft.jzlib.Deflater;
|
||||
import com.jcraft.jzlib.JZlib;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.compression.CompressionException;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
class SpdyHeaderBlockJZlibEncoder extends SpdyHeaderBlockRawEncoder {
|
||||
|
||||
private final Deflater z = new Deflater();
|
||||
|
||||
private boolean finished;
|
||||
|
||||
SpdyHeaderBlockJZlibEncoder(
|
||||
SpdyVersion version, int compressionLevel, int windowBits, int memLevel) {
|
||||
super(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 {
|
||||
resultCode = z.deflateSetDictionary(SPDY_DICT, SPDY_DICT.length);
|
||||
if (resultCode != JZlib.Z_OK) {
|
||||
throw new CompressionException(
|
||||
"failed to set the SPDY dictionary: " + resultCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setInput(ByteBuf decompressed) {
|
||||
int len = decompressed.readableBytes();
|
||||
|
||||
byte[] in;
|
||||
int offset;
|
||||
if (decompressed.hasArray()) {
|
||||
in = decompressed.array();
|
||||
offset = decompressed.arrayOffset() + decompressed.readerIndex();
|
||||
} else {
|
||||
in = new byte[len];
|
||||
decompressed.getBytes(decompressed.readerIndex(), in);
|
||||
offset = 0;
|
||||
}
|
||||
z.next_in = in;
|
||||
z.next_in_index = offset;
|
||||
z.avail_in = len;
|
||||
}
|
||||
|
||||
private ByteBuf encode(ByteBufAllocator alloc) {
|
||||
boolean release = true;
|
||||
ByteBuf out = null;
|
||||
try {
|
||||
int oldNextInIndex = z.next_in_index;
|
||||
int oldNextOutIndex = z.next_out_index;
|
||||
|
||||
int maxOutputLength = (int) Math.ceil(z.next_in.length * 1.001) + 12;
|
||||
out = alloc.heapBuffer(maxOutputLength);
|
||||
z.next_out = out.array();
|
||||
z.next_out_index = out.arrayOffset() + out.writerIndex();
|
||||
z.avail_out = maxOutputLength;
|
||||
|
||||
int resultCode;
|
||||
try {
|
||||
resultCode = z.deflate(JZlib.Z_SYNC_FLUSH);
|
||||
} finally {
|
||||
out.skipBytes(z.next_in_index - oldNextInIndex);
|
||||
}
|
||||
if (resultCode != JZlib.Z_OK) {
|
||||
throw new CompressionException("compression failure: " + resultCode);
|
||||
}
|
||||
|
||||
int outputLength = z.next_out_index - oldNextOutIndex;
|
||||
if (outputLength > 0) {
|
||||
out.writerIndex(out.writerIndex() + outputLength);
|
||||
}
|
||||
release = false;
|
||||
return out;
|
||||
} 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;
|
||||
if (release && out != null) {
|
||||
out.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf encode(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception {
|
||||
if (frame == null) {
|
||||
throw new IllegalArgumentException("frame");
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
ByteBuf decompressed = super.encode(alloc, frame);
|
||||
try {
|
||||
if (!decompressed.isReadable()) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
setInput(decompressed);
|
||||
return encode(alloc);
|
||||
} finally {
|
||||
decompressed.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
z.deflateEnd();
|
||||
z.next_in = null;
|
||||
z.next_out = null;
|
||||
}
|
||||
}
|
@ -1,312 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.getSignedInt;
|
||||
|
||||
public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
|
||||
|
||||
private static final int LENGTH_FIELD_SIZE = 4;
|
||||
|
||||
private final int maxHeaderSize;
|
||||
|
||||
private State state;
|
||||
|
||||
private ByteBuf cumulation;
|
||||
|
||||
private int headerSize;
|
||||
private int numHeaders;
|
||||
private int length;
|
||||
private String name;
|
||||
|
||||
private enum State {
|
||||
READ_NUM_HEADERS,
|
||||
READ_NAME_LENGTH,
|
||||
READ_NAME,
|
||||
SKIP_NAME,
|
||||
READ_VALUE_LENGTH,
|
||||
READ_VALUE,
|
||||
SKIP_VALUE,
|
||||
END_HEADER_BLOCK,
|
||||
ERROR
|
||||
}
|
||||
|
||||
public SpdyHeaderBlockRawDecoder(SpdyVersion spdyVersion, int maxHeaderSize) {
|
||||
if (spdyVersion == null) {
|
||||
throw new NullPointerException("spdyVersion");
|
||||
}
|
||||
this.maxHeaderSize = maxHeaderSize;
|
||||
state = State.READ_NUM_HEADERS;
|
||||
}
|
||||
|
||||
private static int readLengthField(ByteBuf buffer) {
|
||||
int length = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(LENGTH_FIELD_SIZE);
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
void decode(ByteBufAllocator alloc, ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception {
|
||||
if (headerBlock == null) {
|
||||
throw new NullPointerException("headerBlock");
|
||||
}
|
||||
if (frame == null) {
|
||||
throw new NullPointerException("frame");
|
||||
}
|
||||
|
||||
if (cumulation == null) {
|
||||
decodeHeaderBlock(headerBlock, frame);
|
||||
if (headerBlock.isReadable()) {
|
||||
cumulation = alloc.buffer(headerBlock.readableBytes());
|
||||
cumulation.writeBytes(headerBlock);
|
||||
}
|
||||
} else {
|
||||
cumulation.writeBytes(headerBlock);
|
||||
decodeHeaderBlock(cumulation, frame);
|
||||
if (cumulation.isReadable()) {
|
||||
cumulation.discardReadBytes();
|
||||
} else {
|
||||
releaseBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void decodeHeaderBlock(ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception {
|
||||
int skipLength;
|
||||
while (headerBlock.isReadable()) {
|
||||
switch(state) {
|
||||
case READ_NUM_HEADERS:
|
||||
if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
numHeaders = readLengthField(headerBlock);
|
||||
|
||||
if (numHeaders < 0) {
|
||||
state = State.ERROR;
|
||||
frame.setInvalid();
|
||||
} else if (numHeaders == 0) {
|
||||
state = State.END_HEADER_BLOCK;
|
||||
} else {
|
||||
state = State.READ_NAME_LENGTH;
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_NAME_LENGTH:
|
||||
if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
length = readLengthField(headerBlock);
|
||||
|
||||
// Recipients of a zero-length name must issue a stream error
|
||||
if (length <= 0) {
|
||||
state = State.ERROR;
|
||||
frame.setInvalid();
|
||||
} else if (length > maxHeaderSize || headerSize > maxHeaderSize - length) {
|
||||
headerSize = maxHeaderSize + 1;
|
||||
state = State.SKIP_NAME;
|
||||
frame.setTruncated();
|
||||
} else {
|
||||
headerSize += length;
|
||||
state = State.READ_NAME;
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_NAME:
|
||||
if (headerBlock.readableBytes() < length) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] nameBytes = new byte[length];
|
||||
headerBlock.readBytes(nameBytes);
|
||||
name = new String(nameBytes, CharsetUtil.UTF_8);
|
||||
|
||||
// Check for identically named headers
|
||||
if (frame.headers().contains(name)) {
|
||||
state = State.ERROR;
|
||||
frame.setInvalid();
|
||||
} else {
|
||||
state = State.READ_VALUE_LENGTH;
|
||||
}
|
||||
break;
|
||||
|
||||
case SKIP_NAME:
|
||||
skipLength = Math.min(headerBlock.readableBytes(), length);
|
||||
headerBlock.skipBytes(skipLength);
|
||||
length -= skipLength;
|
||||
|
||||
if (length == 0) {
|
||||
state = State.READ_VALUE_LENGTH;
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_VALUE_LENGTH:
|
||||
if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
length = readLengthField(headerBlock);
|
||||
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
if (length < 0) {
|
||||
state = State.ERROR;
|
||||
frame.setInvalid();
|
||||
} else if (length == 0) {
|
||||
if (!frame.isTruncated()) {
|
||||
// SPDY/3 allows zero-length (empty) header values
|
||||
frame.headers().add(name, "");
|
||||
}
|
||||
|
||||
name = null;
|
||||
if (--numHeaders == 0) {
|
||||
state = State.END_HEADER_BLOCK;
|
||||
} else {
|
||||
state = State.READ_NAME_LENGTH;
|
||||
}
|
||||
|
||||
} else if (length > maxHeaderSize || headerSize > maxHeaderSize - length) {
|
||||
headerSize = maxHeaderSize + 1;
|
||||
name = null;
|
||||
state = State.SKIP_VALUE;
|
||||
frame.setTruncated();
|
||||
} else {
|
||||
headerSize += length;
|
||||
state = State.READ_VALUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_VALUE:
|
||||
if (headerBlock.readableBytes() < length) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] valueBytes = new byte[length];
|
||||
headerBlock.readBytes(valueBytes);
|
||||
|
||||
// Add Name/Value pair to headers
|
||||
int index = 0;
|
||||
int offset = 0;
|
||||
|
||||
// Value must not start with a NULL character
|
||||
if (valueBytes[0] == (byte) 0) {
|
||||
state = State.ERROR;
|
||||
frame.setInvalid();
|
||||
break;
|
||||
}
|
||||
|
||||
while (index < length) {
|
||||
while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
|
||||
index ++;
|
||||
}
|
||||
if (index < valueBytes.length) {
|
||||
// Received NULL character
|
||||
if (index + 1 == valueBytes.length || valueBytes[index + 1] == (byte) 0) {
|
||||
// Value field ended with a NULL character or
|
||||
// received multiple, in-sequence NULL characters.
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
state = State.ERROR;
|
||||
frame.setInvalid();
|
||||
break;
|
||||
}
|
||||
}
|
||||
String value = new String(valueBytes, offset, index - offset, CharsetUtil.UTF_8);
|
||||
|
||||
try {
|
||||
frame.headers().add(name, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Name contains NULL or non-ascii characters
|
||||
state = State.ERROR;
|
||||
frame.setInvalid();
|
||||
break;
|
||||
}
|
||||
index ++;
|
||||
offset = index;
|
||||
}
|
||||
|
||||
name = null;
|
||||
|
||||
// If we broke out of the add header loop, break here
|
||||
if (state == State.ERROR) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (--numHeaders == 0) {
|
||||
state = State.END_HEADER_BLOCK;
|
||||
} else {
|
||||
state = State.READ_NAME_LENGTH;
|
||||
}
|
||||
break;
|
||||
|
||||
case SKIP_VALUE:
|
||||
skipLength = Math.min(headerBlock.readableBytes(), length);
|
||||
headerBlock.skipBytes(skipLength);
|
||||
length -= skipLength;
|
||||
|
||||
if (length == 0) {
|
||||
if (--numHeaders == 0) {
|
||||
state = State.END_HEADER_BLOCK;
|
||||
} else {
|
||||
state = State.READ_NAME_LENGTH;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case END_HEADER_BLOCK:
|
||||
state = State.ERROR;
|
||||
frame.setInvalid();
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
headerBlock.skipBytes(headerBlock.readableBytes());
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void endHeaderBlock(SpdyHeadersFrame frame) throws Exception {
|
||||
if (state != State.END_HEADER_BLOCK) {
|
||||
frame.setInvalid();
|
||||
}
|
||||
|
||||
releaseBuffer();
|
||||
|
||||
// Initialize header block decoding fields
|
||||
headerSize = 0;
|
||||
name = null;
|
||||
state = State.READ_NUM_HEADERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
void end() {
|
||||
releaseBuffer();
|
||||
}
|
||||
|
||||
private void releaseBuffer() {
|
||||
if (cumulation != null) {
|
||||
cumulation.release();
|
||||
cumulation = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_MAX_NV_LENGTH;
|
||||
|
||||
public class SpdyHeaderBlockRawEncoder extends SpdyHeaderBlockEncoder {
|
||||
|
||||
private final int version;
|
||||
|
||||
public SpdyHeaderBlockRawEncoder(SpdyVersion version) {
|
||||
if (version == null) {
|
||||
throw new NullPointerException("version");
|
||||
}
|
||||
this.version = version.getVersion();
|
||||
}
|
||||
|
||||
private static void setLengthField(ByteBuf buffer, int writerIndex, int length) {
|
||||
buffer.setInt(writerIndex, length);
|
||||
}
|
||||
|
||||
private static void writeLengthField(ByteBuf buffer, int length) {
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf encode(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception {
|
||||
Set<CharSequence> names = frame.headers().names();
|
||||
int numHeaders = names.size();
|
||||
if (numHeaders == 0) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
if (numHeaders > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"header block contains too many headers");
|
||||
}
|
||||
ByteBuf headerBlock = alloc.heapBuffer();
|
||||
writeLengthField(headerBlock, numHeaders);
|
||||
for (CharSequence name: names) {
|
||||
writeLengthField(headerBlock, name.length());
|
||||
ByteBufUtil.writeAscii(headerBlock, name);
|
||||
int savedIndex = headerBlock.writerIndex();
|
||||
int valueLength = 0;
|
||||
writeLengthField(headerBlock, valueLength);
|
||||
for (CharSequence value: frame.headers().getAll(name)) {
|
||||
int length = value.length();
|
||||
if (length > 0) {
|
||||
ByteBufUtil.writeAscii(headerBlock, value);
|
||||
headerBlock.writeByte(0);
|
||||
valueLength += length + 1;
|
||||
}
|
||||
}
|
||||
if (valueLength != 0) {
|
||||
valueLength --;
|
||||
}
|
||||
if (valueLength > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"header exceeds allowable length: " + name);
|
||||
}
|
||||
if (valueLength > 0) {
|
||||
setLengthField(headerBlock, savedIndex, valueLength);
|
||||
headerBlock.writerIndex(headerBlock.writerIndex() - 1);
|
||||
}
|
||||
}
|
||||
return headerBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
void end() {
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
final class SpdyHeaderBlockZlibDecoder extends SpdyHeaderBlockRawDecoder {
|
||||
|
||||
private static final int DEFAULT_BUFFER_CAPACITY = 4096;
|
||||
private static final SpdyProtocolException INVALID_HEADER_BLOCK =
|
||||
new SpdyProtocolException("Invalid Header Block");
|
||||
|
||||
private final Inflater decompressor = new Inflater();
|
||||
|
||||
private ByteBuf decompressed;
|
||||
|
||||
SpdyHeaderBlockZlibDecoder(SpdyVersion spdyVersion, int maxHeaderSize) {
|
||||
super(spdyVersion, maxHeaderSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
void decode(ByteBufAllocator alloc, ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception {
|
||||
int len = setInput(headerBlock);
|
||||
|
||||
int numBytes;
|
||||
do {
|
||||
numBytes = decompress(alloc, frame);
|
||||
} while (numBytes > 0);
|
||||
|
||||
// z_stream has an internal 64-bit hold buffer
|
||||
// it is always capable of consuming the entire input
|
||||
if (decompressor.getRemaining() != 0) {
|
||||
// we reached the end of the deflate stream
|
||||
throw INVALID_HEADER_BLOCK;
|
||||
}
|
||||
|
||||
headerBlock.skipBytes(len);
|
||||
}
|
||||
|
||||
private int setInput(ByteBuf compressed) {
|
||||
int len = compressed.readableBytes();
|
||||
|
||||
if (compressed.hasArray()) {
|
||||
decompressor.setInput(compressed.array(), compressed.arrayOffset() + compressed.readerIndex(), len);
|
||||
} else {
|
||||
byte[] in = new byte[len];
|
||||
compressed.getBytes(compressed.readerIndex(), in);
|
||||
decompressor.setInput(in, 0, in.length);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
private int decompress(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception {
|
||||
ensureBuffer(alloc);
|
||||
byte[] out = decompressed.array();
|
||||
int off = decompressed.arrayOffset() + decompressed.writerIndex();
|
||||
try {
|
||||
int numBytes = decompressor.inflate(out, off, decompressed.writableBytes());
|
||||
if (numBytes == 0 && decompressor.needsDictionary()) {
|
||||
try {
|
||||
decompressor.setDictionary(SPDY_DICT);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
throw INVALID_HEADER_BLOCK;
|
||||
}
|
||||
numBytes = decompressor.inflate(out, off, decompressed.writableBytes());
|
||||
}
|
||||
if (frame != null) {
|
||||
decompressed.writerIndex(decompressed.writerIndex() + numBytes);
|
||||
decodeHeaderBlock(decompressed, frame);
|
||||
decompressed.discardReadBytes();
|
||||
}
|
||||
|
||||
return numBytes;
|
||||
} catch (DataFormatException e) {
|
||||
throw new SpdyProtocolException("Received invalid header block", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureBuffer(ByteBufAllocator alloc) {
|
||||
if (decompressed == null) {
|
||||
decompressed = alloc.heapBuffer(DEFAULT_BUFFER_CAPACITY);
|
||||
}
|
||||
decompressed.ensureWritable(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
void endHeaderBlock(SpdyHeadersFrame frame) throws Exception {
|
||||
super.endHeaderBlock(frame);
|
||||
releaseBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
super.end();
|
||||
releaseBuffer();
|
||||
decompressor.end();
|
||||
}
|
||||
|
||||
private void releaseBuffer() {
|
||||
if (decompressed != null) {
|
||||
decompressed.release();
|
||||
decompressed = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
class SpdyHeaderBlockZlibEncoder extends SpdyHeaderBlockRawEncoder {
|
||||
|
||||
private final Deflater compressor;
|
||||
|
||||
private boolean finished;
|
||||
|
||||
SpdyHeaderBlockZlibEncoder(SpdyVersion spdyVersion, int compressionLevel) {
|
||||
super(spdyVersion);
|
||||
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||
throw new IllegalArgumentException(
|
||||
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||
}
|
||||
compressor = new Deflater(compressionLevel);
|
||||
compressor.setDictionary(SPDY_DICT);
|
||||
}
|
||||
|
||||
private int setInput(ByteBuf decompressed) {
|
||||
int len = decompressed.readableBytes();
|
||||
|
||||
if (decompressed.hasArray()) {
|
||||
compressor.setInput(decompressed.array(), decompressed.arrayOffset() + decompressed.readerIndex(), len);
|
||||
} else {
|
||||
byte[] in = new byte[len];
|
||||
decompressed.getBytes(decompressed.readerIndex(), in);
|
||||
compressor.setInput(in, 0, in.length);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
private ByteBuf encode(ByteBufAllocator alloc, int len) {
|
||||
ByteBuf compressed = alloc.heapBuffer(len);
|
||||
boolean release = true;
|
||||
try {
|
||||
while (compressInto(compressed)) {
|
||||
// Although unlikely, it's possible that the compressed size is larger than the decompressed size
|
||||
compressed.ensureWritable(compressed.capacity() << 1);
|
||||
}
|
||||
release = false;
|
||||
return compressed;
|
||||
} finally {
|
||||
if (release) {
|
||||
compressed.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean compressInto(ByteBuf compressed) {
|
||||
byte[] out = compressed.array();
|
||||
int off = compressed.arrayOffset() + compressed.writerIndex();
|
||||
int toWrite = compressed.writableBytes();
|
||||
int numBytes = compressor.deflate(out, off, toWrite, Deflater.SYNC_FLUSH);
|
||||
compressed.writerIndex(compressed.writerIndex() + numBytes);
|
||||
return numBytes == toWrite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf encode(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception {
|
||||
if (frame == null) {
|
||||
throw new IllegalArgumentException("frame");
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
ByteBuf decompressed = super.encode(alloc, frame);
|
||||
try {
|
||||
if (!decompressed.isReadable()) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
int len = setInput(decompressed);
|
||||
return encode(alloc, len);
|
||||
} finally {
|
||||
decompressed.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
compressor.end();
|
||||
super.end();
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.Headers;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Provides the constants for the standard SPDY HTTP header names and commonly
|
||||
* used utility methods that access a {@link SpdyHeadersFrame}.
|
||||
*/
|
||||
public interface SpdyHeaders extends Headers<CharSequence, CharSequence, SpdyHeaders> {
|
||||
|
||||
/**
|
||||
* SPDY HTTP header names
|
||||
*/
|
||||
final class HttpNames {
|
||||
/**
|
||||
* {@code ":host"}
|
||||
*/
|
||||
public static final AsciiString HOST = AsciiString.cached(":host");
|
||||
/**
|
||||
* {@code ":method"}
|
||||
*/
|
||||
public static final AsciiString METHOD = AsciiString.cached(":method");
|
||||
/**
|
||||
* {@code ":path"}
|
||||
*/
|
||||
public static final AsciiString PATH = AsciiString.cached(":path");
|
||||
/**
|
||||
* {@code ":scheme"}
|
||||
*/
|
||||
public static final AsciiString SCHEME = AsciiString.cached(":scheme");
|
||||
/**
|
||||
* {@code ":status"}
|
||||
*/
|
||||
public static final AsciiString STATUS = AsciiString.cached(":status");
|
||||
/**
|
||||
* {@code ":version"}
|
||||
*/
|
||||
public static final AsciiString VERSION = AsciiString.cached(":version");
|
||||
|
||||
private HttpNames() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Headers#get(Object)} and convert the result to a {@link String}.
|
||||
* @param name the name of the header to retrieve
|
||||
* @return the first header value if the header is found. {@code null} if there's no such header.
|
||||
*/
|
||||
String getAsString(CharSequence name);
|
||||
|
||||
/**
|
||||
* {@link Headers#getAll(Object)} and convert each element of {@link List} to a {@link String}.
|
||||
* @param name the name of the header to retrieve
|
||||
* @return a {@link List} of header values or an empty {@link List} if no values are found.
|
||||
*/
|
||||
List<String> getAllAsString(CharSequence name);
|
||||
|
||||
/**
|
||||
* {@link #iterator()} that converts each {@link Entry}'s key and value to a {@link String}.
|
||||
*/
|
||||
Iterator<Entry<String, String>> iteratorAsString();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise.
|
||||
* <p>
|
||||
* If {@code ignoreCase} is {@code true} then a case insensitive compare is done on the value.
|
||||
* @param name the name of the header to find
|
||||
* @param value the value of the header to find
|
||||
* @param ignoreCase {@code true} then a case insensitive compare is run to compare values.
|
||||
* otherwise a case sensitive compare is run to compare values.
|
||||
*/
|
||||
boolean contains(CharSequence name, CharSequence value, boolean ignoreCase);
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdyHeadersFrame extends SpdyStreamFrame {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
SpdyHeadersFrame setInvalid();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this header block has been truncated due to
|
||||
* length restrictions.
|
||||
*/
|
||||
boolean isTruncated();
|
||||
|
||||
/**
|
||||
* Mark this header block as truncated.
|
||||
*/
|
||||
SpdyHeadersFrame setTruncated();
|
||||
|
||||
/**
|
||||
* Returns the {@link SpdyHeaders}.
|
||||
*/
|
||||
SpdyHeaders headers();
|
||||
|
||||
@Override
|
||||
SpdyHeadersFrame setStreamId(int streamID);
|
||||
|
||||
@Override
|
||||
SpdyHeadersFrame setLast(boolean last);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.CombinedChannelDuplexHandler;
|
||||
|
||||
/**
|
||||
* A combination of {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}
|
||||
*/
|
||||
public final class SpdyHttpCodec extends CombinedChannelDuplexHandler<SpdyHttpDecoder, SpdyHttpEncoder> {
|
||||
/**
|
||||
* Creates a new instance with the specified decoder options.
|
||||
*/
|
||||
public SpdyHttpCodec(SpdyVersion version, int maxContentLength) {
|
||||
super(new SpdyHttpDecoder(version, maxContentLength), new SpdyHttpEncoder(version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified decoder options.
|
||||
*/
|
||||
public SpdyHttpCodec(SpdyVersion version, int maxContentLength, boolean validateHttpHeaders) {
|
||||
super(new SpdyHttpDecoder(version, maxContentLength, validateHttpHeaders), new SpdyHttpEncoder(version));
|
||||
}
|
||||
}
|
@ -1,435 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpMessage;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositive;
|
||||
|
||||
/**
|
||||
* Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
|
||||
* and {@link SpdyDataFrame}s into {@link FullHttpRequest}s and {@link FullHttpResponse}s.
|
||||
*/
|
||||
public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
|
||||
private final boolean validateHeaders;
|
||||
private final int spdyVersion;
|
||||
private final int maxContentLength;
|
||||
private final Map<Integer, FullHttpMessage> messageMap;
|
||||
|
||||
/**
|
||||
* 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(SpdyVersion version, int maxContentLength) {
|
||||
this(version, maxContentLength, new HashMap<>(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param validateHeaders {@code true} if http headers should be validated
|
||||
*/
|
||||
public SpdyHttpDecoder(SpdyVersion version, int maxContentLength, boolean validateHeaders) {
|
||||
this(version, maxContentLength, new HashMap<>(), validateHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*
|
||||
* @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.
|
||||
* @param messageMap the {@link Map} used to hold partially received messages.
|
||||
*/
|
||||
protected SpdyHttpDecoder(SpdyVersion version, int maxContentLength, Map<Integer, FullHttpMessage> messageMap) {
|
||||
this(version, maxContentLength, messageMap, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*
|
||||
* @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.
|
||||
* @param messageMap the {@link Map} used to hold partially received messages.
|
||||
* @param validateHeaders {@code true} if http headers should be validated
|
||||
*/
|
||||
protected SpdyHttpDecoder(SpdyVersion version, int maxContentLength, Map<Integer,
|
||||
FullHttpMessage> messageMap, boolean validateHeaders) {
|
||||
if (version == null) {
|
||||
throw new NullPointerException("version");
|
||||
}
|
||||
checkPositive(maxContentLength, "maxContentLength");
|
||||
spdyVersion = version.getVersion();
|
||||
this.maxContentLength = maxContentLength;
|
||||
this.messageMap = messageMap;
|
||||
this.validateHeaders = validateHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
// Release any outstanding messages from the map
|
||||
for (Map.Entry<Integer, FullHttpMessage> entry : messageMap.entrySet()) {
|
||||
ReferenceCountUtil.safeRelease(entry.getValue());
|
||||
}
|
||||
messageMap.clear();
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
protected FullHttpMessage putMessage(int streamId, FullHttpMessage message) {
|
||||
return messageMap.put(streamId, message);
|
||||
}
|
||||
|
||||
protected FullHttpMessage getMessage(int streamId) {
|
||||
return messageMap.get(streamId);
|
||||
}
|
||||
|
||||
protected FullHttpMessage removeMessage(int streamId) {
|
||||
return messageMap.remove(streamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, SpdyFrame msg, List<Object> out)
|
||||
throws Exception {
|
||||
if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
// HTTP requests/responses are mapped one-to-one to SPDY streams.
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
int streamId = spdySynStreamFrame.streamId();
|
||||
|
||||
if (SpdyCodecUtil.isServerId(streamId)) {
|
||||
// SYN_STREAM frames initiated by the server are pushed resources
|
||||
int associatedToStreamId = spdySynStreamFrame.associatedStreamId();
|
||||
|
||||
// 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.writeAndFlush(spdyRstStreamFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a client receives a SYN_STREAM with isLast set,
|
||||
// reply with a RST_STREAM with error code PROTOCOL_ERROR
|
||||
// (we only support pushed resources divided into two header blocks).
|
||||
if (spdySynStreamFrame.isLast()) {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a client receives a response with a truncated header block,
|
||||
// reply with a RST_STREAM with error code INTERNAL_ERROR.
|
||||
if (spdySynStreamFrame.isTruncated()) {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR);
|
||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
FullHttpRequest httpRequestWithEntity = createHttpRequest(spdySynStreamFrame, ctx.alloc());
|
||||
|
||||
// Set the Stream-ID, Associated-To-Stream-ID, and Priority as headers
|
||||
httpRequestWithEntity.headers().setInt(Names.STREAM_ID, streamId);
|
||||
httpRequestWithEntity.headers().setInt(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
|
||||
httpRequestWithEntity.headers().setInt(Names.PRIORITY, spdySynStreamFrame.priority());
|
||||
|
||||
out.add(httpRequestWithEntity);
|
||||
|
||||
} catch (Throwable ignored) {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||
}
|
||||
} else {
|
||||
// SYN_STREAM frames initiated by the client are HTTP requests
|
||||
|
||||
// If a client sends a request with a truncated header block, the server must
|
||||
// reply with a HTTP 431 REQUEST HEADER FIELDS TOO LARGE reply.
|
||||
if (spdySynStreamFrame.isTruncated()) {
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||
spdySynReplyFrame.setLast(true);
|
||||
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
|
||||
frameHeaders.setInt(STATUS, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE.code());
|
||||
frameHeaders.setObject(VERSION, HttpVersion.HTTP_1_0);
|
||||
ctx.writeAndFlush(spdySynReplyFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
FullHttpRequest httpRequestWithEntity = createHttpRequest(spdySynStreamFrame, ctx.alloc());
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
httpRequestWithEntity.headers().setInt(Names.STREAM_ID, streamId);
|
||||
|
||||
if (spdySynStreamFrame.isLast()) {
|
||||
out.add(httpRequestWithEntity);
|
||||
} else {
|
||||
// Request body will follow in a series of Data Frames
|
||||
putMessage(streamId, httpRequestWithEntity);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// If a client sends a SYN_STREAM without all of the getMethod, 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 frameHeaders = spdySynReplyFrame.headers();
|
||||
frameHeaders.setInt(STATUS, HttpResponseStatus.BAD_REQUEST.code());
|
||||
frameHeaders.setObject(VERSION, HttpVersion.HTTP_1_0);
|
||||
ctx.writeAndFlush(spdySynReplyFrame);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
int streamId = spdySynReplyFrame.streamId();
|
||||
|
||||
// If a client receives a SYN_REPLY with a truncated header block,
|
||||
// reply with a RST_STREAM frame with error code INTERNAL_ERROR.
|
||||
if (spdySynReplyFrame.isTruncated()) {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR);
|
||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
FullHttpResponse httpResponseWithEntity =
|
||||
createHttpResponse(spdySynReplyFrame, ctx.alloc(), validateHeaders);
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
httpResponseWithEntity.headers().setInt(Names.STREAM_ID, streamId);
|
||||
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
HttpUtil.setContentLength(httpResponseWithEntity, 0);
|
||||
out.add(httpResponseWithEntity);
|
||||
} else {
|
||||
// Response body will follow in a series of Data Frames
|
||||
putMessage(streamId, httpResponseWithEntity);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// If a client receives a SYN_REPLY without valid getStatus and version headers
|
||||
// the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
int streamId = spdyHeadersFrame.streamId();
|
||||
FullHttpMessage fullHttpMessage = getMessage(streamId);
|
||||
|
||||
if (fullHttpMessage == null) {
|
||||
// HEADERS frames may initiate a pushed response
|
||||
if (SpdyCodecUtil.isServerId(streamId)) {
|
||||
|
||||
// If a client receives a HEADERS with a truncated header block,
|
||||
// reply with a RST_STREAM frame with error code INTERNAL_ERROR.
|
||||
if (spdyHeadersFrame.isTruncated()) {
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INTERNAL_ERROR);
|
||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fullHttpMessage = createHttpResponse(spdyHeadersFrame, ctx.alloc(), validateHeaders);
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
fullHttpMessage.headers().setInt(Names.STREAM_ID, streamId);
|
||||
|
||||
if (spdyHeadersFrame.isLast()) {
|
||||
HttpUtil.setContentLength(fullHttpMessage, 0);
|
||||
out.add(fullHttpMessage);
|
||||
} else {
|
||||
// Response body will follow in a series of Data Frames
|
||||
putMessage(streamId, fullHttpMessage);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// If a client receives a SYN_REPLY without valid getStatus and version headers
|
||||
// the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR
|
||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore trailers in a truncated HEADERS frame.
|
||||
if (!spdyHeadersFrame.isTruncated()) {
|
||||
for (Map.Entry<CharSequence, CharSequence> e: spdyHeadersFrame.headers()) {
|
||||
fullHttpMessage.headers().add(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (spdyHeadersFrame.isLast()) {
|
||||
HttpUtil.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
|
||||
removeMessage(streamId);
|
||||
out.add(fullHttpMessage);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
int streamId = spdyDataFrame.streamId();
|
||||
FullHttpMessage fullHttpMessage = getMessage(streamId);
|
||||
|
||||
// If message is not in map discard Data Frame.
|
||||
if (fullHttpMessage == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuf content = fullHttpMessage.content();
|
||||
if (content.readableBytes() > maxContentLength - spdyDataFrame.content().readableBytes()) {
|
||||
removeMessage(streamId);
|
||||
throw new TooLongFrameException(
|
||||
"HTTP content length exceeded " + maxContentLength + " bytes.");
|
||||
}
|
||||
|
||||
ByteBuf spdyDataFrameData = spdyDataFrame.content();
|
||||
int spdyDataFrameDataLen = spdyDataFrameData.readableBytes();
|
||||
content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);
|
||||
|
||||
if (spdyDataFrame.isLast()) {
|
||||
HttpUtil.setContentLength(fullHttpMessage, content.readableBytes());
|
||||
removeMessage(streamId);
|
||||
out.add(fullHttpMessage);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
int streamId = spdyRstStreamFrame.streamId();
|
||||
removeMessage(streamId);
|
||||
}
|
||||
}
|
||||
|
||||
private static FullHttpRequest createHttpRequest(SpdyHeadersFrame requestFrame, ByteBufAllocator alloc)
|
||||
throws Exception {
|
||||
// Create the first line of the request from the name/value pairs
|
||||
SpdyHeaders headers = requestFrame.headers();
|
||||
HttpMethod method = HttpMethod.valueOf(headers.getAsString(METHOD));
|
||||
String url = headers.getAsString(PATH);
|
||||
HttpVersion httpVersion = HttpVersion.valueOf(headers.getAsString(VERSION));
|
||||
headers.remove(METHOD);
|
||||
headers.remove(PATH);
|
||||
headers.remove(VERSION);
|
||||
|
||||
boolean release = true;
|
||||
ByteBuf buffer = alloc.buffer();
|
||||
try {
|
||||
FullHttpRequest req = new DefaultFullHttpRequest(httpVersion, method, url, buffer);
|
||||
|
||||
// Remove the scheme header
|
||||
headers.remove(SCHEME);
|
||||
|
||||
// Replace the SPDY host header with the HTTP host header
|
||||
CharSequence host = headers.get(HOST);
|
||||
headers.remove(HOST);
|
||||
req.headers().set(HttpHeaderNames.HOST, host);
|
||||
|
||||
for (Map.Entry<CharSequence, CharSequence> e : requestFrame.headers()) {
|
||||
req.headers().add(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
// The Connection and Keep-Alive headers are no longer valid
|
||||
HttpUtil.setKeepAlive(req, true);
|
||||
|
||||
// Transfer-Encoding header is not valid
|
||||
req.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
|
||||
release = false;
|
||||
return req;
|
||||
} finally {
|
||||
if (release) {
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static FullHttpResponse createHttpResponse(SpdyHeadersFrame responseFrame, ByteBufAllocator alloc,
|
||||
boolean validateHeaders) throws Exception {
|
||||
|
||||
// Create the first line of the response from the name/value pairs
|
||||
SpdyHeaders headers = responseFrame.headers();
|
||||
HttpResponseStatus status = HttpResponseStatus.parseLine(headers.get(STATUS));
|
||||
HttpVersion version = HttpVersion.valueOf(headers.getAsString(VERSION));
|
||||
headers.remove(STATUS);
|
||||
headers.remove(VERSION);
|
||||
|
||||
boolean release = true;
|
||||
ByteBuf buffer = alloc.buffer();
|
||||
try {
|
||||
FullHttpResponse res = new DefaultFullHttpResponse(version, status, buffer, validateHeaders);
|
||||
for (Map.Entry<CharSequence, CharSequence> e: responseFrame.headers()) {
|
||||
res.headers().add(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
// The Connection and Keep-Alive headers are no longer valid
|
||||
HttpUtil.setKeepAlive(res, true);
|
||||
|
||||
// Transfer-Encoding header is not valid
|
||||
res.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
|
||||
res.headers().remove(HttpHeaderNames.TRAILER);
|
||||
|
||||
release = false;
|
||||
return res;
|
||||
} finally {
|
||||
if (release) {
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,332 +0,0 @@
|
||||
/*
|
||||
* 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.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
||||
import io.netty.handler.codec.http.FullHttpMessage;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpContent}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 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 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>
|
||||
* </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 HttpContent}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<HttpObject> {
|
||||
|
||||
private int currentStreamId;
|
||||
|
||||
private final boolean validateHeaders;
|
||||
private final boolean headersToLowerCase;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param version the protocol version
|
||||
*/
|
||||
public SpdyHttpEncoder(SpdyVersion version) {
|
||||
this(version, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param version the protocol version
|
||||
* @param headersToLowerCase convert header names to lowercase. In a controlled environment,
|
||||
* one can disable the conversion.
|
||||
* @param validateHeaders validate the header names and values when adding them to the {@link SpdyHeaders}
|
||||
*/
|
||||
public SpdyHttpEncoder(SpdyVersion version, boolean headersToLowerCase, boolean validateHeaders) {
|
||||
if (version == null) {
|
||||
throw new NullPointerException("version");
|
||||
}
|
||||
this.headersToLowerCase = headersToLowerCase;
|
||||
this.validateHeaders = validateHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out) throws Exception {
|
||||
|
||||
boolean valid = false;
|
||||
boolean last = false;
|
||||
|
||||
if (msg instanceof HttpRequest) {
|
||||
|
||||
HttpRequest httpRequest = (HttpRequest) msg;
|
||||
SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest);
|
||||
out.add(spdySynStreamFrame);
|
||||
|
||||
last = spdySynStreamFrame.isLast() || spdySynStreamFrame.isUnidirectional();
|
||||
valid = true;
|
||||
}
|
||||
if (msg instanceof HttpResponse) {
|
||||
|
||||
HttpResponse httpResponse = (HttpResponse) msg;
|
||||
SpdyHeadersFrame spdyHeadersFrame = createHeadersFrame(httpResponse);
|
||||
out.add(spdyHeadersFrame);
|
||||
|
||||
last = spdyHeadersFrame.isLast();
|
||||
valid = true;
|
||||
}
|
||||
if (msg instanceof HttpContent && !last) {
|
||||
|
||||
HttpContent chunk = (HttpContent) msg;
|
||||
|
||||
chunk.content().retain();
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId, chunk.content());
|
||||
if (chunk instanceof LastHttpContent) {
|
||||
LastHttpContent trailer = (LastHttpContent) chunk;
|
||||
HttpHeaders trailers = trailer.trailingHeaders();
|
||||
if (trailers.isEmpty()) {
|
||||
spdyDataFrame.setLast(true);
|
||||
out.add(spdyDataFrame);
|
||||
} else {
|
||||
// Create SPDY HEADERS frame out of trailers
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId, validateHeaders);
|
||||
spdyHeadersFrame.setLast(true);
|
||||
Iterator<Entry<CharSequence, CharSequence>> itr = trailers.iteratorCharSequence();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<CharSequence, CharSequence> entry = itr.next();
|
||||
final CharSequence headerName =
|
||||
headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
|
||||
spdyHeadersFrame.headers().add(headerName, entry.getValue());
|
||||
}
|
||||
|
||||
// Write DATA frame and append HEADERS frame
|
||||
out.add(spdyDataFrame);
|
||||
out.add(spdyHeadersFrame);
|
||||
}
|
||||
} else {
|
||||
out.add(spdyDataFrame);
|
||||
}
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
throw new UnsupportedMessageTypeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private SpdySynStreamFrame createSynStreamFrame(HttpRequest httpRequest) throws Exception {
|
||||
// Get the Stream-ID, Associated-To-Stream-ID, Priority, and scheme from the headers
|
||||
final HttpHeaders httpHeaders = httpRequest.headers();
|
||||
int streamId = httpHeaders.getInt(SpdyHttpHeaders.Names.STREAM_ID);
|
||||
int associatedToStreamId = httpHeaders.getInt(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID, 0);
|
||||
byte priority = (byte) httpHeaders.getInt(SpdyHttpHeaders.Names.PRIORITY, 0);
|
||||
CharSequence scheme = httpHeaders.get(SpdyHttpHeaders.Names.SCHEME);
|
||||
httpHeaders.remove(SpdyHttpHeaders.Names.STREAM_ID);
|
||||
httpHeaders.remove(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID);
|
||||
httpHeaders.remove(SpdyHttpHeaders.Names.PRIORITY);
|
||||
httpHeaders.remove(SpdyHttpHeaders.Names.SCHEME);
|
||||
|
||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
||||
// headers are not valid and MUST not be sent.
|
||||
httpHeaders.remove(HttpHeaderNames.CONNECTION);
|
||||
httpHeaders.remove("Keep-Alive");
|
||||
httpHeaders.remove("Proxy-Connection");
|
||||
httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders);
|
||||
|
||||
// Unfold the first line of the message into name/value pairs
|
||||
SpdyHeaders frameHeaders = spdySynStreamFrame.headers();
|
||||
frameHeaders.set(SpdyHeaders.HttpNames.METHOD, httpRequest.method().name());
|
||||
frameHeaders.set(SpdyHeaders.HttpNames.PATH, httpRequest.uri());
|
||||
frameHeaders.set(SpdyHeaders.HttpNames.VERSION, httpRequest.protocolVersion().text());
|
||||
|
||||
// Replace the HTTP host header with the SPDY host header
|
||||
CharSequence host = httpHeaders.get(HttpHeaderNames.HOST);
|
||||
httpHeaders.remove(HttpHeaderNames.HOST);
|
||||
frameHeaders.set(SpdyHeaders.HttpNames.HOST, host);
|
||||
|
||||
// Set the SPDY scheme header
|
||||
if (scheme == null) {
|
||||
scheme = "https";
|
||||
}
|
||||
frameHeaders.set(SpdyHeaders.HttpNames.SCHEME, scheme);
|
||||
|
||||
// Transfer the remaining HTTP headers
|
||||
Iterator<Entry<CharSequence, CharSequence>> itr = httpHeaders.iteratorCharSequence();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<CharSequence, CharSequence> entry = itr.next();
|
||||
final CharSequence headerName =
|
||||
headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
|
||||
frameHeaders.add(headerName, entry.getValue());
|
||||
}
|
||||
currentStreamId = spdySynStreamFrame.streamId();
|
||||
if (associatedToStreamId == 0) {
|
||||
spdySynStreamFrame.setLast(isLast(httpRequest));
|
||||
} else {
|
||||
spdySynStreamFrame.setUnidirectional(true);
|
||||
}
|
||||
|
||||
return spdySynStreamFrame;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private SpdyHeadersFrame createHeadersFrame(HttpResponse httpResponse) throws Exception {
|
||||
// Get the Stream-ID from the headers
|
||||
final HttpHeaders httpHeaders = httpResponse.headers();
|
||||
int streamId = httpHeaders.getInt(SpdyHttpHeaders.Names.STREAM_ID);
|
||||
httpHeaders.remove(SpdyHttpHeaders.Names.STREAM_ID);
|
||||
|
||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
||||
// headers are not valid and MUST not be sent.
|
||||
httpHeaders.remove(HttpHeaderNames.CONNECTION);
|
||||
httpHeaders.remove("Keep-Alive");
|
||||
httpHeaders.remove("Proxy-Connection");
|
||||
httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame;
|
||||
if (SpdyCodecUtil.isServerId(streamId)) {
|
||||
spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders);
|
||||
} else {
|
||||
spdyHeadersFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders);
|
||||
}
|
||||
SpdyHeaders frameHeaders = spdyHeadersFrame.headers();
|
||||
// Unfold the first line of the response into name/value pairs
|
||||
frameHeaders.set(SpdyHeaders.HttpNames.STATUS, httpResponse.status().codeAsText());
|
||||
frameHeaders.set(SpdyHeaders.HttpNames.VERSION, httpResponse.protocolVersion().text());
|
||||
|
||||
// Transfer the remaining HTTP headers
|
||||
Iterator<Entry<CharSequence, CharSequence>> itr = httpHeaders.iteratorCharSequence();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<CharSequence, CharSequence> entry = itr.next();
|
||||
final CharSequence headerName =
|
||||
headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
|
||||
spdyHeadersFrame.headers().add(headerName, entry.getValue());
|
||||
}
|
||||
|
||||
currentStreamId = streamId;
|
||||
spdyHeadersFrame.setLast(isLast(httpResponse));
|
||||
|
||||
return spdyHeadersFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given HTTP message should be considered as a last SPDY frame.
|
||||
*
|
||||
* @param httpMessage check this HTTP message
|
||||
* @return whether the given HTTP message should generate a <em>last</em> SPDY frame.
|
||||
*/
|
||||
private static boolean isLast(HttpMessage httpMessage) {
|
||||
if (httpMessage instanceof FullHttpMessage) {
|
||||
FullHttpMessage fullMessage = (FullHttpMessage) httpMessage;
|
||||
if (fullMessage.trailingHeaders().isEmpty() && !fullMessage.content().isReadable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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.AsciiString;
|
||||
|
||||
/**
|
||||
* Provides the constants for the header names and the utility methods
|
||||
* used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}.
|
||||
*/
|
||||
public final class SpdyHttpHeaders {
|
||||
|
||||
/**
|
||||
* SPDY HTTP header names
|
||||
*/
|
||||
public static final class Names {
|
||||
/**
|
||||
* {@code "x-spdy-stream-id"}
|
||||
*/
|
||||
public static final AsciiString STREAM_ID = AsciiString.cached("x-spdy-stream-id");
|
||||
/**
|
||||
* {@code "x-spdy-associated-to-stream-id"}
|
||||
*/
|
||||
public static final AsciiString ASSOCIATED_TO_STREAM_ID = AsciiString.cached("x-spdy-associated-to-stream-id");
|
||||
/**
|
||||
* {@code "x-spdy-priority"}
|
||||
*/
|
||||
public static final AsciiString PRIORITY = AsciiString.cached("x-spdy-priority");
|
||||
/**
|
||||
* {@code "x-spdy-scheme"}
|
||||
*/
|
||||
public static final AsciiString SCHEME = AsciiString.cached("x-spdy-scheme");
|
||||
|
||||
private Names() { }
|
||||
}
|
||||
|
||||
private SpdyHttpHeaders() { }
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageCodec;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* {@link MessageToMessageCodec} that takes care of adding the right {@link SpdyHttpHeaders.Names#STREAM_ID} to the
|
||||
* {@link HttpMessage} if one is not present. This makes it possible to just re-use plan handlers current used
|
||||
* for HTTP.
|
||||
*/
|
||||
public class SpdyHttpResponseStreamIdHandler extends
|
||||
MessageToMessageCodec<Object, HttpMessage> {
|
||||
private static final Integer NO_ID = -1;
|
||||
private final Queue<Integer> ids = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public boolean acceptInboundMessage(Object msg) throws Exception {
|
||||
return msg instanceof HttpMessage || msg instanceof SpdyRstStreamFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, HttpMessage msg, List<Object> out) throws Exception {
|
||||
Integer id = ids.poll();
|
||||
if (id != null && id.intValue() != NO_ID && !msg.headers().contains(SpdyHttpHeaders.Names.STREAM_ID)) {
|
||||
msg.headers().setInt(Names.STREAM_ID, id);
|
||||
}
|
||||
|
||||
out.add(ReferenceCountUtil.retain(msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
|
||||
if (msg instanceof HttpMessage) {
|
||||
boolean contains = ((HttpMessage) msg).headers().contains(SpdyHttpHeaders.Names.STREAM_ID);
|
||||
if (!contains) {
|
||||
ids.add(NO_ID);
|
||||
} else {
|
||||
ids.add(((HttpMessage) msg).headers().getInt(Names.STREAM_ID));
|
||||
}
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
ids.remove(((SpdyRstStreamFrame) msg).streamId());
|
||||
}
|
||||
|
||||
out.add(ReferenceCountUtil.retain(msg));
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdyPingFrame extends SpdyFrame {
|
||||
|
||||
/**
|
||||
* Returns the ID of this frame.
|
||||
*/
|
||||
int id();
|
||||
|
||||
/**
|
||||
* Sets the ID of this frame.
|
||||
*/
|
||||
SpdyPingFrame setId(int id);
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
public class SpdyProtocolException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 7870000537743847264L;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public SpdyProtocolException() { }
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdyRstStreamFrame extends SpdyStreamFrame {
|
||||
|
||||
/**
|
||||
* Returns the status of this frame.
|
||||
*/
|
||||
SpdyStreamStatus status();
|
||||
|
||||
/**
|
||||
* Sets the status of this frame.
|
||||
*/
|
||||
SpdyRstStreamFrame setStatus(SpdyStreamStatus status);
|
||||
|
||||
@Override
|
||||
SpdyRstStreamFrame setStreamId(int streamId);
|
||||
|
||||
@Override
|
||||
SpdyRstStreamFrame setLast(boolean last);
|
||||
}
|
@ -1,357 +0,0 @@
|
||||
/*
|
||||
* 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.ChannelPromise;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
final class SpdySession {
|
||||
|
||||
private final AtomicInteger activeLocalStreams = new AtomicInteger();
|
||||
private final AtomicInteger activeRemoteStreams = new AtomicInteger();
|
||||
private final Map<Integer, StreamState> activeStreams = new ConcurrentHashMap<>();
|
||||
private final StreamComparator streamComparator = new StreamComparator();
|
||||
private final AtomicInteger sendWindowSize;
|
||||
private final AtomicInteger receiveWindowSize;
|
||||
|
||||
SpdySession(int sendWindowSize, int receiveWindowSize) {
|
||||
this.sendWindowSize = new AtomicInteger(sendWindowSize);
|
||||
this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
|
||||
}
|
||||
|
||||
int numActiveStreams(boolean remote) {
|
||||
if (remote) {
|
||||
return activeRemoteStreams.get();
|
||||
} else {
|
||||
return activeLocalStreams.get();
|
||||
}
|
||||
}
|
||||
|
||||
boolean noActiveStreams() {
|
||||
return activeStreams.isEmpty();
|
||||
}
|
||||
|
||||
boolean isActiveStream(int streamId) {
|
||||
return activeStreams.containsKey(streamId);
|
||||
}
|
||||
|
||||
// Stream-IDs should be iterated in priority order
|
||||
Map<Integer, StreamState> activeStreams() {
|
||||
Map<Integer, StreamState> streams = new TreeMap<>(streamComparator);
|
||||
streams.putAll(activeStreams);
|
||||
return streams;
|
||||
}
|
||||
|
||||
void acceptStream(
|
||||
int streamId, byte priority, boolean remoteSideClosed, boolean localSideClosed,
|
||||
int sendWindowSize, int receiveWindowSize, boolean remote) {
|
||||
if (!remoteSideClosed || !localSideClosed) {
|
||||
StreamState state = activeStreams.put(streamId, new StreamState(
|
||||
priority, remoteSideClosed, localSideClosed, sendWindowSize, receiveWindowSize));
|
||||
if (state == null) {
|
||||
if (remote) {
|
||||
activeRemoteStreams.incrementAndGet();
|
||||
} else {
|
||||
activeLocalStreams.incrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StreamState removeActiveStream(int streamId, boolean remote) {
|
||||
StreamState state = activeStreams.remove(streamId);
|
||||
if (state != null) {
|
||||
if (remote) {
|
||||
activeRemoteStreams.decrementAndGet();
|
||||
} else {
|
||||
activeLocalStreams.decrementAndGet();
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
void removeStream(int streamId, Throwable cause, boolean remote) {
|
||||
StreamState state = removeActiveStream(streamId, remote);
|
||||
if (state != null) {
|
||||
state.clearPendingWrites(cause);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isRemoteSideClosed(int streamId) {
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state == null || state.isRemoteSideClosed();
|
||||
}
|
||||
|
||||
void closeRemoteSide(int streamId, boolean remote) {
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
if (state != null) {
|
||||
state.closeRemoteSide();
|
||||
if (state.isLocalSideClosed()) {
|
||||
removeActiveStream(streamId, remote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isLocalSideClosed(int streamId) {
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state == null || state.isLocalSideClosed();
|
||||
}
|
||||
|
||||
void closeLocalSide(int streamId, boolean remote) {
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
if (state != null) {
|
||||
state.closeLocalSide();
|
||||
if (state.isRemoteSideClosed()) {
|
||||
removeActiveStream(streamId, remote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* hasReceivedReply and receivedReply are only called from channelRead()
|
||||
* no need to synchronize access to the StreamState
|
||||
*/
|
||||
boolean hasReceivedReply(int streamId) {
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state != null && state.hasReceivedReply();
|
||||
}
|
||||
|
||||
void receivedReply(int streamId) {
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
if (state != null) {
|
||||
state.receivedReply();
|
||||
}
|
||||
}
|
||||
|
||||
int getSendWindowSize(int streamId) {
|
||||
if (streamId == SPDY_SESSION_STREAM_ID) {
|
||||
return sendWindowSize.get();
|
||||
}
|
||||
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state != null ? state.getSendWindowSize() : -1;
|
||||
}
|
||||
|
||||
int updateSendWindowSize(int streamId, int deltaWindowSize) {
|
||||
if (streamId == SPDY_SESSION_STREAM_ID) {
|
||||
return sendWindowSize.addAndGet(deltaWindowSize);
|
||||
}
|
||||
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state != null ? state.updateSendWindowSize(deltaWindowSize) : -1;
|
||||
}
|
||||
|
||||
int updateReceiveWindowSize(int streamId, int deltaWindowSize) {
|
||||
if (streamId == SPDY_SESSION_STREAM_ID) {
|
||||
return receiveWindowSize.addAndGet(deltaWindowSize);
|
||||
}
|
||||
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
if (state == null) {
|
||||
return -1;
|
||||
}
|
||||
if (deltaWindowSize > 0) {
|
||||
state.setReceiveWindowSizeLowerBound(0);
|
||||
}
|
||||
return state.updateReceiveWindowSize(deltaWindowSize);
|
||||
}
|
||||
|
||||
int getReceiveWindowSizeLowerBound(int streamId) {
|
||||
if (streamId == SPDY_SESSION_STREAM_ID) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state != null ? state.getReceiveWindowSizeLowerBound() : 0;
|
||||
}
|
||||
|
||||
void updateAllSendWindowSizes(int deltaWindowSize) {
|
||||
for (StreamState state: activeStreams.values()) {
|
||||
state.updateSendWindowSize(deltaWindowSize);
|
||||
}
|
||||
}
|
||||
|
||||
void updateAllReceiveWindowSizes(int deltaWindowSize) {
|
||||
for (StreamState state: activeStreams.values()) {
|
||||
state.updateReceiveWindowSize(deltaWindowSize);
|
||||
if (deltaWindowSize < 0) {
|
||||
state.setReceiveWindowSizeLowerBound(deltaWindowSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean putPendingWrite(int streamId, PendingWrite pendingWrite) {
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state != null && state.putPendingWrite(pendingWrite);
|
||||
}
|
||||
|
||||
PendingWrite getPendingWrite(int streamId) {
|
||||
if (streamId == SPDY_SESSION_STREAM_ID) {
|
||||
for (Map.Entry<Integer, StreamState> e: activeStreams().entrySet()) {
|
||||
StreamState state = e.getValue();
|
||||
if (state.getSendWindowSize() > 0) {
|
||||
PendingWrite pendingWrite = state.getPendingWrite();
|
||||
if (pendingWrite != null) {
|
||||
return pendingWrite;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state != null ? state.getPendingWrite() : null;
|
||||
}
|
||||
|
||||
PendingWrite removePendingWrite(int streamId) {
|
||||
StreamState state = activeStreams.get(streamId);
|
||||
return state != null ? state.removePendingWrite() : null;
|
||||
}
|
||||
|
||||
private static final class StreamState {
|
||||
|
||||
private final byte priority;
|
||||
private boolean remoteSideClosed;
|
||||
private boolean localSideClosed;
|
||||
private boolean receivedReply;
|
||||
private final AtomicInteger sendWindowSize;
|
||||
private final AtomicInteger receiveWindowSize;
|
||||
private int receiveWindowSizeLowerBound;
|
||||
private final Queue<PendingWrite> pendingWriteQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
StreamState(
|
||||
byte priority, boolean remoteSideClosed, boolean localSideClosed,
|
||||
int sendWindowSize, int receiveWindowSize) {
|
||||
this.priority = priority;
|
||||
this.remoteSideClosed = remoteSideClosed;
|
||||
this.localSideClosed = localSideClosed;
|
||||
this.sendWindowSize = new AtomicInteger(sendWindowSize);
|
||||
this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
|
||||
}
|
||||
|
||||
byte getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
boolean isRemoteSideClosed() {
|
||||
return remoteSideClosed;
|
||||
}
|
||||
|
||||
void closeRemoteSide() {
|
||||
remoteSideClosed = true;
|
||||
}
|
||||
|
||||
boolean isLocalSideClosed() {
|
||||
return localSideClosed;
|
||||
}
|
||||
|
||||
void closeLocalSide() {
|
||||
localSideClosed = true;
|
||||
}
|
||||
|
||||
boolean hasReceivedReply() {
|
||||
return receivedReply;
|
||||
}
|
||||
|
||||
void receivedReply() {
|
||||
receivedReply = true;
|
||||
}
|
||||
|
||||
int getSendWindowSize() {
|
||||
return sendWindowSize.get();
|
||||
}
|
||||
|
||||
int updateSendWindowSize(int deltaWindowSize) {
|
||||
return sendWindowSize.addAndGet(deltaWindowSize);
|
||||
}
|
||||
|
||||
int updateReceiveWindowSize(int deltaWindowSize) {
|
||||
return receiveWindowSize.addAndGet(deltaWindowSize);
|
||||
}
|
||||
|
||||
int getReceiveWindowSizeLowerBound() {
|
||||
return receiveWindowSizeLowerBound;
|
||||
}
|
||||
|
||||
void setReceiveWindowSizeLowerBound(int receiveWindowSizeLowerBound) {
|
||||
this.receiveWindowSizeLowerBound = receiveWindowSizeLowerBound;
|
||||
}
|
||||
|
||||
boolean putPendingWrite(PendingWrite msg) {
|
||||
return pendingWriteQueue.offer(msg);
|
||||
}
|
||||
|
||||
PendingWrite getPendingWrite() {
|
||||
return pendingWriteQueue.peek();
|
||||
}
|
||||
|
||||
PendingWrite removePendingWrite() {
|
||||
return pendingWriteQueue.poll();
|
||||
}
|
||||
|
||||
void clearPendingWrites(Throwable cause) {
|
||||
for (;;) {
|
||||
PendingWrite pendingWrite = pendingWriteQueue.poll();
|
||||
if (pendingWrite == null) {
|
||||
break;
|
||||
}
|
||||
pendingWrite.fail(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class StreamComparator implements Comparator<Integer> {
|
||||
|
||||
StreamComparator() { }
|
||||
|
||||
@Override
|
||||
public int compare(Integer id1, Integer id2) {
|
||||
StreamState state1 = activeStreams.get(id1);
|
||||
StreamState state2 = activeStreams.get(id2);
|
||||
|
||||
int result = state1.getPriority() - state2.getPriority();
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return id1 - id2;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PendingWrite {
|
||||
final SpdyDataFrame spdyDataFrame;
|
||||
final ChannelPromise promise;
|
||||
|
||||
PendingWrite(SpdyDataFrame spdyDataFrame, ChannelPromise promise) {
|
||||
this.spdyDataFrame = spdyDataFrame;
|
||||
this.promise = promise;
|
||||
}
|
||||
|
||||
void fail(Throwable cause) {
|
||||
spdyDataFrame.release();
|
||||
promise.setFailure(cause);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,845 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.internal.ThrowableUtil;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SESSION_STREAM_ID;
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.isServerId;
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||
|
||||
/**
|
||||
* Manages streams within a SPDY session.
|
||||
*/
|
||||
public class SpdySessionHandler extends ChannelDuplexHandler {
|
||||
|
||||
private static final SpdyProtocolException PROTOCOL_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
||||
new SpdyProtocolException(), SpdySessionHandler.class, "handleOutboundMessage(...)");
|
||||
private static final SpdyProtocolException STREAM_CLOSED = ThrowableUtil.unknownStackTrace(
|
||||
new SpdyProtocolException("Stream closed"), SpdySessionHandler.class, "removeStream(...)");
|
||||
|
||||
private static final int DEFAULT_WINDOW_SIZE = 64 * 1024; // 64 KB default initial window size
|
||||
private int initialSendWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
private int initialReceiveWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
private volatile int initialSessionReceiveWindowSize = DEFAULT_WINDOW_SIZE;
|
||||
|
||||
private final SpdySession spdySession = new SpdySession(initialSendWindowSize, initialReceiveWindowSize);
|
||||
private int lastGoodStreamId;
|
||||
|
||||
private static final int DEFAULT_MAX_CONCURRENT_STREAMS = Integer.MAX_VALUE;
|
||||
private int remoteConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS;
|
||||
private int localConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS;
|
||||
|
||||
private final AtomicInteger pings = new AtomicInteger();
|
||||
|
||||
private boolean sentGoAwayFrame;
|
||||
private boolean receivedGoAwayFrame;
|
||||
|
||||
private ChannelFutureListener closeSessionFutureListener;
|
||||
|
||||
private final boolean server;
|
||||
private final int minorVersion;
|
||||
|
||||
/**
|
||||
* Creates a new session handler.
|
||||
*
|
||||
* @param version the protocol version
|
||||
* @param server {@code true} if and only if this session handler should
|
||||
* handle the server endpoint of the connection.
|
||||
* {@code false} if and only if this session handler should
|
||||
* handle the client endpoint of the connection.
|
||||
*/
|
||||
public SpdySessionHandler(SpdyVersion version, boolean server) {
|
||||
if (version == null) {
|
||||
throw new NullPointerException("version");
|
||||
}
|
||||
this.server = server;
|
||||
minorVersion = version.getMinorVersion();
|
||||
}
|
||||
|
||||
public void setSessionReceiveWindowSize(int sessionReceiveWindowSize) {
|
||||
checkPositiveOrZero(sessionReceiveWindowSize, "sessionReceiveWindowSize");
|
||||
// This will not send a window update frame immediately.
|
||||
// If this value increases the allowed receive window size,
|
||||
// a WINDOW_UPDATE frame will be sent when only half of the
|
||||
// session window size remains during data frame processing.
|
||||
// If this value decreases the allowed receive window size,
|
||||
// the window will be reduced as data frames are processed.
|
||||
initialSessionReceiveWindowSize = sessionReceiveWindowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
/*
|
||||
* SPDY Data frame processing requirements:
|
||||
*
|
||||
* If an endpoint receives a data frame for a Stream-ID which is not open
|
||||
* and the endpoint has not sent a GOAWAY frame, it must issue a stream error
|
||||
* with the error code INVALID_STREAM for the Stream-ID.
|
||||
*
|
||||
* If an endpoint which created the stream receives a data frame before receiving
|
||||
* a SYN_REPLY on that stream, it is a protocol error, and the recipient must
|
||||
* issue a stream error with the getStatus code PROTOCOL_ERROR for the Stream-ID.
|
||||
*
|
||||
* If an endpoint receives multiple data frames for invalid Stream-IDs,
|
||||
* it may close the session.
|
||||
*
|
||||
* If an endpoint refuses a stream it must ignore any data frames for that stream.
|
||||
*
|
||||
* If an endpoint receives a data frame after the stream is half-closed from the
|
||||
* sender, it must send a RST_STREAM frame with the getStatus STREAM_ALREADY_CLOSED.
|
||||
*
|
||||
* If an endpoint receives a data frame after the stream is closed, it must send
|
||||
* a RST_STREAM frame with the getStatus PROTOCOL_ERROR.
|
||||
*/
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
int streamId = spdyDataFrame.streamId();
|
||||
|
||||
int deltaWindowSize = -1 * spdyDataFrame.content().readableBytes();
|
||||
int newSessionWindowSize =
|
||||
spdySession.updateReceiveWindowSize(SPDY_SESSION_STREAM_ID, deltaWindowSize);
|
||||
|
||||
// Check if session window size is reduced beyond allowable lower bound
|
||||
if (newSessionWindowSize < 0) {
|
||||
issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a WINDOW_UPDATE frame if less than half the session window size remains
|
||||
if (newSessionWindowSize <= initialSessionReceiveWindowSize / 2) {
|
||||
int sessionDeltaWindowSize = initialSessionReceiveWindowSize - newSessionWindowSize;
|
||||
spdySession.updateReceiveWindowSize(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame =
|
||||
new DefaultSpdyWindowUpdateFrame(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
|
||||
ctx.writeAndFlush(spdyWindowUpdateFrame);
|
||||
}
|
||||
|
||||
// Check if we received a data frame for a Stream-ID which is not open
|
||||
|
||||
if (!spdySession.isActiveStream(streamId)) {
|
||||
spdyDataFrame.release();
|
||||
if (streamId <= lastGoodStreamId) {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
} else if (!sentGoAwayFrame) {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we received a data frame for a stream which is half-closed
|
||||
|
||||
if (spdySession.isRemoteSideClosed(streamId)) {
|
||||
spdyDataFrame.release();
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.STREAM_ALREADY_CLOSED);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we received a data frame before receiving a SYN_REPLY
|
||||
if (!isRemoteInitiatedId(streamId) && !spdySession.hasReceivedReply(streamId)) {
|
||||
spdyDataFrame.release();
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SPDY Data frame flow control processing requirements:
|
||||
*
|
||||
* Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.
|
||||
*/
|
||||
|
||||
// Update receive window size
|
||||
int newWindowSize = spdySession.updateReceiveWindowSize(streamId, deltaWindowSize);
|
||||
|
||||
// Window size can become negative if we sent a SETTINGS frame that reduces the
|
||||
// size of the transfer window after the peer has written data frames.
|
||||
// The value is bounded by the length that SETTINGS frame decrease the window.
|
||||
// This difference is stored for the session when writing the SETTINGS frame
|
||||
// and is cleared once we send a WINDOW_UPDATE frame.
|
||||
if (newWindowSize < spdySession.getReceiveWindowSizeLowerBound(streamId)) {
|
||||
spdyDataFrame.release();
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.FLOW_CONTROL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Window size became negative due to sender writing frame before receiving SETTINGS
|
||||
// Send data frames upstream in initialReceiveWindowSize chunks
|
||||
if (newWindowSize < 0) {
|
||||
while (spdyDataFrame.content().readableBytes() > initialReceiveWindowSize) {
|
||||
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
|
||||
streamId, spdyDataFrame.content().readRetainedSlice(initialReceiveWindowSize));
|
||||
ctx.writeAndFlush(partialDataFrame);
|
||||
}
|
||||
}
|
||||
|
||||
// Send a WINDOW_UPDATE frame if less than half the stream window size remains
|
||||
if (newWindowSize <= initialReceiveWindowSize / 2 && !spdyDataFrame.isLast()) {
|
||||
int streamDeltaWindowSize = initialReceiveWindowSize - newWindowSize;
|
||||
spdySession.updateReceiveWindowSize(streamId, streamDeltaWindowSize);
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame =
|
||||
new DefaultSpdyWindowUpdateFrame(streamId, streamDeltaWindowSize);
|
||||
ctx.writeAndFlush(spdyWindowUpdateFrame);
|
||||
}
|
||||
|
||||
// Close the remote side of the stream if this is the last frame
|
||||
if (spdyDataFrame.isLast()) {
|
||||
halfCloseStream(streamId, true, ctx.newSucceededFuture());
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
/*
|
||||
* SPDY SYN_STREAM frame processing requirements:
|
||||
*
|
||||
* If an endpoint receives a SYN_STREAM with a Stream-ID that is less than
|
||||
* any previously received SYN_STREAM, it must issue a session error with
|
||||
* the getStatus PROTOCOL_ERROR.
|
||||
*
|
||||
* If an endpoint receives multiple SYN_STREAM frames with the same active
|
||||
* Stream-ID, it must issue a stream error with the getStatus code PROTOCOL_ERROR.
|
||||
*
|
||||
* The recipient can reject a stream by sending a stream error with the
|
||||
* getStatus code REFUSED_STREAM.
|
||||
*/
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
int streamId = spdySynStreamFrame.streamId();
|
||||
|
||||
// Check if we received a valid SYN_STREAM frame
|
||||
if (spdySynStreamFrame.isInvalid() ||
|
||||
!isRemoteInitiatedId(streamId) ||
|
||||
spdySession.isActiveStream(streamId)) {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stream-IDs must be monotonically increasing
|
||||
if (streamId <= lastGoodStreamId) {
|
||||
issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to accept the stream
|
||||
byte priority = spdySynStreamFrame.priority();
|
||||
boolean remoteSideClosed = spdySynStreamFrame.isLast();
|
||||
boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
|
||||
if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.REFUSED_STREAM);
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
/*
|
||||
* SPDY SYN_REPLY frame processing requirements:
|
||||
*
|
||||
* If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID
|
||||
* it must issue a stream error with the getStatus code STREAM_IN_USE.
|
||||
*/
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
int streamId = spdySynReplyFrame.streamId();
|
||||
|
||||
// Check if we received a valid SYN_REPLY frame
|
||||
if (spdySynReplyFrame.isInvalid() ||
|
||||
isRemoteInitiatedId(streamId) ||
|
||||
spdySession.isRemoteSideClosed(streamId)) {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have received multiple frames for the same Stream-ID
|
||||
if (spdySession.hasReceivedReply(streamId)) {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.STREAM_IN_USE);
|
||||
return;
|
||||
}
|
||||
|
||||
spdySession.receivedReply(streamId);
|
||||
|
||||
// Close the remote side of the stream if this is the last frame
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
halfCloseStream(streamId, true, ctx.newSucceededFuture());
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
/*
|
||||
* SPDY RST_STREAM frame processing requirements:
|
||||
*
|
||||
* After receiving a RST_STREAM on a stream, the receiver must not send
|
||||
* additional frames on that stream.
|
||||
*
|
||||
* An endpoint must not send a RST_STREAM in response to a RST_STREAM.
|
||||
*/
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
removeStream(spdyRstStreamFrame.streamId(), ctx.newSucceededFuture());
|
||||
|
||||
} else if (msg instanceof SpdySettingsFrame) {
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
|
||||
int settingsMinorVersion = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION);
|
||||
if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) {
|
||||
// Settings frame had the wrong minor version
|
||||
issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
int newConcurrentStreams =
|
||||
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
if (newConcurrentStreams >= 0) {
|
||||
remoteConcurrentStreams = newConcurrentStreams;
|
||||
}
|
||||
|
||||
// Persistence flag are inconsistent with the use of SETTINGS to communicate
|
||||
// the initial window size. Remove flags from the sender requesting that the
|
||||
// value be persisted. Remove values that the sender indicates are persisted.
|
||||
if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
|
||||
spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
}
|
||||
spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
|
||||
|
||||
int newInitialWindowSize =
|
||||
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
if (newInitialWindowSize >= 0) {
|
||||
updateInitialSendWindowSize(newInitialWindowSize);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
/*
|
||||
* SPDY PING frame processing requirements:
|
||||
*
|
||||
* Receivers of a PING frame should send an identical frame to the sender
|
||||
* as soon as possible.
|
||||
*
|
||||
* Receivers of a PING frame must ignore frames that it did not initiate
|
||||
*/
|
||||
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
|
||||
if (isRemoteInitiatedId(spdyPingFrame.id())) {
|
||||
ctx.writeAndFlush(spdyPingFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: only checks that there are outstanding pings since uniqueness is not enforced
|
||||
if (pings.get() == 0) {
|
||||
return;
|
||||
}
|
||||
pings.getAndDecrement();
|
||||
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
receivedGoAwayFrame = true;
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
int streamId = spdyHeadersFrame.streamId();
|
||||
|
||||
// Check if we received a valid HEADERS frame
|
||||
if (spdyHeadersFrame.isInvalid()) {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spdySession.isRemoteSideClosed(streamId)) {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the remote side of the stream if this is the last frame
|
||||
if (spdyHeadersFrame.isLast()) {
|
||||
halfCloseStream(streamId, true, ctx.newSucceededFuture());
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyWindowUpdateFrame) {
|
||||
|
||||
/*
|
||||
* SPDY WINDOW_UPDATE frame processing requirements:
|
||||
*
|
||||
* Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31
|
||||
* must send a RST_STREAM with the getStatus code FLOW_CONTROL_ERROR.
|
||||
*
|
||||
* Sender should ignore all WINDOW_UPDATE frames associated with a stream
|
||||
* after sending the last frame for the stream.
|
||||
*/
|
||||
|
||||
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
|
||||
int streamId = spdyWindowUpdateFrame.streamId();
|
||||
int deltaWindowSize = spdyWindowUpdateFrame.deltaWindowSize();
|
||||
|
||||
// Ignore frames for half-closed streams
|
||||
if (streamId != SPDY_SESSION_STREAM_ID && spdySession.isLocalSideClosed(streamId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for numerical overflow
|
||||
if (spdySession.getSendWindowSize(streamId) > Integer.MAX_VALUE - deltaWindowSize) {
|
||||
if (streamId == SPDY_SESSION_STREAM_ID) {
|
||||
issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
|
||||
} else {
|
||||
issueStreamError(ctx, streamId, SpdyStreamStatus.FLOW_CONTROL_ERROR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
updateSendWindowSize(ctx, streamId, deltaWindowSize);
|
||||
}
|
||||
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
for (Integer streamId: spdySession.activeStreams().keySet()) {
|
||||
removeStream(streamId, ctx.newSucceededFuture());
|
||||
}
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (cause instanceof SpdyProtocolException) {
|
||||
issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
ctx.fireExceptionCaught(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||
sendGoAwayFrame(ctx, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (msg instanceof SpdyDataFrame ||
|
||||
msg instanceof SpdySynStreamFrame ||
|
||||
msg instanceof SpdySynReplyFrame ||
|
||||
msg instanceof SpdyRstStreamFrame ||
|
||||
msg instanceof SpdySettingsFrame ||
|
||||
msg instanceof SpdyPingFrame ||
|
||||
msg instanceof SpdyGoAwayFrame ||
|
||||
msg instanceof SpdyHeadersFrame ||
|
||||
msg instanceof SpdyWindowUpdateFrame) {
|
||||
|
||||
handleOutboundMessage(ctx, msg, promise);
|
||||
} else {
|
||||
ctx.write(msg, promise);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleOutboundMessage(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (msg instanceof SpdyDataFrame) {
|
||||
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
int streamId = spdyDataFrame.streamId();
|
||||
|
||||
// Frames must not be sent on half-closed streams
|
||||
if (spdySession.isLocalSideClosed(streamId)) {
|
||||
spdyDataFrame.release();
|
||||
promise.setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SPDY Data frame flow control processing requirements:
|
||||
*
|
||||
* Sender must not send a data frame with data length greater
|
||||
* than the transfer window size.
|
||||
*
|
||||
* After sending each data frame, the sender decrements its
|
||||
* transfer window size by the amount of data transmitted.
|
||||
*
|
||||
* When the window size becomes less than or equal to 0, the
|
||||
* sender must pause transmitting data frames.
|
||||
*/
|
||||
|
||||
int dataLength = spdyDataFrame.content().readableBytes();
|
||||
int sendWindowSize = spdySession.getSendWindowSize(streamId);
|
||||
int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID);
|
||||
sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize);
|
||||
|
||||
if (sendWindowSize <= 0) {
|
||||
// Stream is stalled -- enqueue Data frame and return
|
||||
spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise));
|
||||
return;
|
||||
} else if (sendWindowSize < dataLength) {
|
||||
// Stream is not stalled but we cannot send the entire frame
|
||||
spdySession.updateSendWindowSize(streamId, -1 * sendWindowSize);
|
||||
spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize);
|
||||
|
||||
// Create a partial data frame whose length is the current window size
|
||||
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
|
||||
streamId, spdyDataFrame.content().readRetainedSlice(sendWindowSize));
|
||||
|
||||
// Enqueue the remaining data (will be the first frame queued)
|
||||
spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise));
|
||||
|
||||
// The transfer window size is pre-decremented when sending a data frame downstream.
|
||||
// Close the session on write failures that leave the transfer window in a corrupt state.
|
||||
final ChannelHandlerContext context = ctx;
|
||||
ctx.write(partialDataFrame).addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR);
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
// Window size is large enough to send entire data frame
|
||||
spdySession.updateSendWindowSize(streamId, -1 * dataLength);
|
||||
spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataLength);
|
||||
|
||||
// The transfer window size is pre-decremented when sending a data frame downstream.
|
||||
// Close the session on write failures that leave the transfer window in a corrupt state.
|
||||
final ChannelHandlerContext context = ctx;
|
||||
promise.addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Close the local side of the stream if this is the last frame
|
||||
if (spdyDataFrame.isLast()) {
|
||||
halfCloseStream(streamId, false, promise);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
int streamId = spdySynStreamFrame.streamId();
|
||||
|
||||
if (isRemoteInitiatedId(streamId)) {
|
||||
promise.setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
byte priority = spdySynStreamFrame.priority();
|
||||
boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional();
|
||||
boolean localSideClosed = spdySynStreamFrame.isLast();
|
||||
if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) {
|
||||
promise.setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
int streamId = spdySynReplyFrame.streamId();
|
||||
|
||||
// Frames must not be sent on half-closed streams
|
||||
if (!isRemoteInitiatedId(streamId) || spdySession.isLocalSideClosed(streamId)) {
|
||||
promise.setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the local side of the stream if this is the last frame
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
halfCloseStream(streamId, false, promise);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
removeStream(spdyRstStreamFrame.streamId(), promise);
|
||||
|
||||
} else if (msg instanceof SpdySettingsFrame) {
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
|
||||
int settingsMinorVersion = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION);
|
||||
if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) {
|
||||
// Settings frame had the wrong minor version
|
||||
promise.setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
int newConcurrentStreams =
|
||||
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
if (newConcurrentStreams >= 0) {
|
||||
localConcurrentStreams = newConcurrentStreams;
|
||||
}
|
||||
|
||||
// Persistence flag are inconsistent with the use of SETTINGS to communicate
|
||||
// the initial window size. Remove flags from the sender requesting that the
|
||||
// value be persisted. Remove values that the sender indicates are persisted.
|
||||
if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
|
||||
spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
}
|
||||
spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
|
||||
|
||||
int newInitialWindowSize =
|
||||
spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
|
||||
if (newInitialWindowSize >= 0) {
|
||||
updateInitialReceiveWindowSize(newInitialWindowSize);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyPingFrame) {
|
||||
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
if (isRemoteInitiatedId(spdyPingFrame.id())) {
|
||||
ctx.fireExceptionCaught(new IllegalArgumentException(
|
||||
"invalid PING ID: " + spdyPingFrame.id()));
|
||||
return;
|
||||
}
|
||||
pings.getAndIncrement();
|
||||
|
||||
} else if (msg instanceof SpdyGoAwayFrame) {
|
||||
|
||||
// Why is this being sent? Intercept it and fail the write.
|
||||
// Should have sent a CLOSE ChannelStateEvent
|
||||
promise.setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
int streamId = spdyHeadersFrame.streamId();
|
||||
|
||||
// Frames must not be sent on half-closed streams
|
||||
if (spdySession.isLocalSideClosed(streamId)) {
|
||||
promise.setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the local side of the stream if this is the last frame
|
||||
if (spdyHeadersFrame.isLast()) {
|
||||
halfCloseStream(streamId, false, promise);
|
||||
}
|
||||
|
||||
} else if (msg instanceof SpdyWindowUpdateFrame) {
|
||||
|
||||
// Why is this being sent? Intercept it and fail the write.
|
||||
promise.setFailure(PROTOCOL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.write(msg, promise);
|
||||
}
|
||||
|
||||
/*
|
||||
* SPDY Session Error Handling:
|
||||
*
|
||||
* When a session error occurs, the endpoint encountering the error must first
|
||||
* send a GOAWAY frame with the Stream-ID of the most recently received stream
|
||||
* from the remote endpoint, and the error code for why the session is terminating.
|
||||
*
|
||||
* After sending the GOAWAY frame, the endpoint must close the TCP connection.
|
||||
*/
|
||||
private void issueSessionError(
|
||||
ChannelHandlerContext ctx, SpdySessionStatus status) {
|
||||
|
||||
sendGoAwayFrame(ctx, status).addListener(new ClosingChannelFutureListener(ctx, ctx.newPromise()));
|
||||
}
|
||||
|
||||
/*
|
||||
* SPDY Stream Error Handling:
|
||||
*
|
||||
* Upon a stream error, the endpoint must send a RST_STREAM frame which contains
|
||||
* the Stream-ID for the stream where the error occurred and the error getStatus which
|
||||
* caused the error.
|
||||
*
|
||||
* After sending the RST_STREAM, the stream is closed to the sending endpoint.
|
||||
*
|
||||
* Note: this is only called by the worker thread
|
||||
*/
|
||||
private void issueStreamError(ChannelHandlerContext ctx, int streamId, SpdyStreamStatus status) {
|
||||
boolean fireChannelRead = !spdySession.isRemoteSideClosed(streamId);
|
||||
ChannelPromise promise = ctx.newPromise();
|
||||
removeStream(streamId, promise);
|
||||
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, status);
|
||||
ctx.writeAndFlush(spdyRstStreamFrame, promise);
|
||||
if (fireChannelRead) {
|
||||
ctx.fireChannelRead(spdyRstStreamFrame);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
private boolean isRemoteInitiatedId(int id) {
|
||||
boolean serverId = isServerId(id);
|
||||
return server && !serverId || !server && serverId;
|
||||
}
|
||||
|
||||
// need to synchronize to prevent new streams from being created while updating active streams
|
||||
private void updateInitialSendWindowSize(int newInitialWindowSize) {
|
||||
int deltaWindowSize = newInitialWindowSize - initialSendWindowSize;
|
||||
initialSendWindowSize = newInitialWindowSize;
|
||||
spdySession.updateAllSendWindowSizes(deltaWindowSize);
|
||||
}
|
||||
|
||||
// need to synchronize to prevent new streams from being created while updating active streams
|
||||
private void updateInitialReceiveWindowSize(int newInitialWindowSize) {
|
||||
int deltaWindowSize = newInitialWindowSize - initialReceiveWindowSize;
|
||||
initialReceiveWindowSize = newInitialWindowSize;
|
||||
spdySession.updateAllReceiveWindowSizes(deltaWindowSize);
|
||||
}
|
||||
|
||||
// need to synchronize accesses to sentGoAwayFrame, lastGoodStreamId, and initial window sizes
|
||||
private boolean acceptStream(
|
||||
int streamId, byte priority, boolean remoteSideClosed, boolean localSideClosed) {
|
||||
// Cannot initiate any new streams after receiving or sending GOAWAY
|
||||
if (receivedGoAwayFrame || sentGoAwayFrame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean remote = isRemoteInitiatedId(streamId);
|
||||
int maxConcurrentStreams = remote ? localConcurrentStreams : remoteConcurrentStreams;
|
||||
if (spdySession.numActiveStreams(remote) >= maxConcurrentStreams) {
|
||||
return false;
|
||||
}
|
||||
spdySession.acceptStream(
|
||||
streamId, priority, remoteSideClosed, localSideClosed,
|
||||
initialSendWindowSize, initialReceiveWindowSize, remote);
|
||||
if (remote) {
|
||||
lastGoodStreamId = streamId;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void halfCloseStream(int streamId, boolean remote, ChannelFuture future) {
|
||||
if (remote) {
|
||||
spdySession.closeRemoteSide(streamId, isRemoteInitiatedId(streamId));
|
||||
} else {
|
||||
spdySession.closeLocalSide(streamId, isRemoteInitiatedId(streamId));
|
||||
}
|
||||
if (closeSessionFutureListener != null && spdySession.noActiveStreams()) {
|
||||
future.addListener(closeSessionFutureListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeStream(int streamId, ChannelFuture future) {
|
||||
spdySession.removeStream(streamId, STREAM_CLOSED, isRemoteInitiatedId(streamId));
|
||||
|
||||
if (closeSessionFutureListener != null && spdySession.noActiveStreams()) {
|
||||
future.addListener(closeSessionFutureListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSendWindowSize(final ChannelHandlerContext ctx, int streamId, int deltaWindowSize) {
|
||||
spdySession.updateSendWindowSize(streamId, deltaWindowSize);
|
||||
|
||||
while (true) {
|
||||
// Check if we have unblocked a stalled stream
|
||||
SpdySession.PendingWrite pendingWrite = spdySession.getPendingWrite(streamId);
|
||||
if (pendingWrite == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SpdyDataFrame spdyDataFrame = pendingWrite.spdyDataFrame;
|
||||
int dataFrameSize = spdyDataFrame.content().readableBytes();
|
||||
int writeStreamId = spdyDataFrame.streamId();
|
||||
int sendWindowSize = spdySession.getSendWindowSize(writeStreamId);
|
||||
int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID);
|
||||
sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize);
|
||||
|
||||
if (sendWindowSize <= 0) {
|
||||
return;
|
||||
} else if (sendWindowSize < dataFrameSize) {
|
||||
// We can send a partial frame
|
||||
spdySession.updateSendWindowSize(writeStreamId, -1 * sendWindowSize);
|
||||
spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize);
|
||||
|
||||
// Create a partial data frame whose length is the current window size
|
||||
SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
|
||||
writeStreamId, spdyDataFrame.content().readRetainedSlice(sendWindowSize));
|
||||
|
||||
// The transfer window size is pre-decremented when sending a data frame downstream.
|
||||
// Close the session on write failures that leave the transfer window in a corrupt state.
|
||||
ctx.writeAndFlush(partialDataFrame).addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
issueSessionError(ctx, SpdySessionStatus.INTERNAL_ERROR);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Window size is large enough to send entire data frame
|
||||
spdySession.removePendingWrite(writeStreamId);
|
||||
spdySession.updateSendWindowSize(writeStreamId, -1 * dataFrameSize);
|
||||
spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataFrameSize);
|
||||
|
||||
// Close the local side of the stream if this is the last frame
|
||||
if (spdyDataFrame.isLast()) {
|
||||
halfCloseStream(writeStreamId, false, pendingWrite.promise);
|
||||
}
|
||||
|
||||
// The transfer window size is pre-decremented when sending a data frame downstream.
|
||||
// Close the session on write failures that leave the transfer window in a corrupt state.
|
||||
ctx.writeAndFlush(spdyDataFrame, pendingWrite.promise).addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
issueSessionError(ctx, SpdySessionStatus.INTERNAL_ERROR);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelPromise future) {
|
||||
// Avoid NotYetConnectedException
|
||||
if (!ctx.channel().isActive()) {
|
||||
ctx.close(future);
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelFuture f = sendGoAwayFrame(ctx, SpdySessionStatus.OK);
|
||||
if (spdySession.noActiveStreams()) {
|
||||
f.addListener(new ClosingChannelFutureListener(ctx, future));
|
||||
} else {
|
||||
closeSessionFutureListener = new ClosingChannelFutureListener(ctx, future);
|
||||
}
|
||||
// FIXME: Close the connection forcibly after timeout.
|
||||
}
|
||||
|
||||
private ChannelFuture sendGoAwayFrame(
|
||||
ChannelHandlerContext ctx, SpdySessionStatus status) {
|
||||
if (!sentGoAwayFrame) {
|
||||
sentGoAwayFrame = true;
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamId, status);
|
||||
return ctx.writeAndFlush(spdyGoAwayFrame);
|
||||
} else {
|
||||
return ctx.newSucceededFuture();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ClosingChannelFutureListener implements ChannelFutureListener {
|
||||
private final ChannelHandlerContext ctx;
|
||||
private final ChannelPromise promise;
|
||||
|
||||
ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelPromise promise) {
|
||||
this.ctx = ctx;
|
||||
this.promise = promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture sentGoAwayFuture) throws Exception {
|
||||
ctx.close(promise);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.
|
||||
*/
|
||||
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");
|
||||
|
||||
/**
|
||||
* 2 Internal Error
|
||||
*/
|
||||
public static final SpdySessionStatus INTERNAL_ERROR =
|
||||
new SpdySessionStatus(2, "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 2:
|
||||
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 code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status phrase of this status.
|
||||
*/
|
||||
public String statusPhrase() {
|
||||
return statusPhrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return code();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SpdySessionStatus)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return code() == ((SpdySessionStatus) o).code();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return statusPhrase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SpdySessionStatus o) {
|
||||
return code() - o.code();
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdySettingsFrame extends SpdyFrame {
|
||||
|
||||
int SETTINGS_MINOR_VERSION = 0;
|
||||
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> ids();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the setting ID has a value.
|
||||
*/
|
||||
boolean isSet(int id);
|
||||
|
||||
/**
|
||||
* Returns the value of the setting ID.
|
||||
* Returns -1 if the setting ID is not set.
|
||||
*/
|
||||
int getValue(int id);
|
||||
|
||||
/**
|
||||
* Sets the value of the setting ID.
|
||||
* The ID cannot be negative and cannot exceed 16777215.
|
||||
*/
|
||||
SpdySettingsFrame 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 cannot be negative and cannot exceed 16777215.
|
||||
*/
|
||||
SpdySettingsFrame setValue(int id, int value, boolean persistVal, boolean persisted);
|
||||
|
||||
/**
|
||||
* Removes the value of the setting ID.
|
||||
* Removes all persistence information for the setting.
|
||||
*/
|
||||
SpdySettingsFrame 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 isPersistValue(int id);
|
||||
|
||||
/**
|
||||
* Sets if this setting should be persisted.
|
||||
* Has no effect if the setting ID has no value.
|
||||
*/
|
||||
SpdySettingsFrame 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.
|
||||
*/
|
||||
SpdySettingsFrame 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.
|
||||
*/
|
||||
SpdySettingsFrame setClearPreviouslyPersistedSettings(boolean clear);
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame that is associated with an individual SPDY Stream
|
||||
*/
|
||||
public interface SpdyStreamFrame extends SpdyFrame {
|
||||
|
||||
/**
|
||||
* Returns the Stream-ID of this frame.
|
||||
*/
|
||||
int streamId();
|
||||
|
||||
/**
|
||||
* Sets the Stream-ID of this frame. The Stream-ID must be positive.
|
||||
*/
|
||||
SpdyStreamFrame 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.
|
||||
*/
|
||||
SpdyStreamFrame setLast(boolean last);
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.
|
||||
*/
|
||||
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 code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status phrase of this status.
|
||||
*/
|
||||
public String statusPhrase() {
|
||||
return statusPhrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return code();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SpdyStreamStatus)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return code() == ((SpdyStreamStatus) o).code();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return statusPhrase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SpdyStreamStatus o) {
|
||||
return code() - o.code();
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdySynReplyFrame extends SpdyHeadersFrame {
|
||||
|
||||
@Override
|
||||
SpdySynReplyFrame setStreamId(int streamID);
|
||||
|
||||
@Override
|
||||
SpdySynReplyFrame setLast(boolean last);
|
||||
|
||||
@Override
|
||||
SpdySynReplyFrame setInvalid();
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdySynStreamFrame extends SpdyHeadersFrame {
|
||||
|
||||
/**
|
||||
* Returns the Associated-To-Stream-ID of this frame.
|
||||
*/
|
||||
int associatedStreamId();
|
||||
|
||||
/**
|
||||
* Sets the Associated-To-Stream-ID of this frame.
|
||||
* The Associated-To-Stream-ID cannot be negative.
|
||||
*/
|
||||
SpdySynStreamFrame setAssociatedStreamId(int associatedStreamId);
|
||||
|
||||
/**
|
||||
* Returns the priority of the stream.
|
||||
*/
|
||||
byte priority();
|
||||
|
||||
/**
|
||||
* Sets the priority of the stream.
|
||||
* The priority must be between 0 and 7 inclusive.
|
||||
*/
|
||||
SpdySynStreamFrame setPriority(byte priority);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
SpdySynStreamFrame setUnidirectional(boolean unidirectional);
|
||||
|
||||
@Override
|
||||
SpdySynStreamFrame setStreamId(int streamID);
|
||||
|
||||
@Override
|
||||
SpdySynStreamFrame setLast(boolean last);
|
||||
|
||||
@Override
|
||||
SpdySynStreamFrame setInvalid();
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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;
|
||||
|
||||
public enum SpdyVersion {
|
||||
SPDY_3_1 (3, 1);
|
||||
|
||||
private final int version;
|
||||
private final int minorVersion;
|
||||
|
||||
SpdyVersion(int version, int minorVersion) {
|
||||
this.version = version;
|
||||
this.minorVersion = minorVersion;
|
||||
}
|
||||
|
||||
int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
int getMinorVersion() {
|
||||
return minorVersion;
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 Frame
|
||||
*/
|
||||
public interface SpdyWindowUpdateFrame extends SpdyFrame {
|
||||
|
||||
/**
|
||||
* Returns the Stream-ID of this frame.
|
||||
*/
|
||||
int streamId();
|
||||
|
||||
/**
|
||||
* Sets the Stream-ID of this frame. The Stream-ID cannot be negative.
|
||||
*/
|
||||
SpdyWindowUpdateFrame setStreamId(int streamID);
|
||||
|
||||
/**
|
||||
* Returns the Delta-Window-Size of this frame.
|
||||
*/
|
||||
int deltaWindowSize();
|
||||
|
||||
/**
|
||||
* Sets the Delta-Window-Size of this frame.
|
||||
* The Delta-Window-Size must be positive.
|
||||
*/
|
||||
SpdyWindowUpdateFrame setDeltaWindowSize(int deltaWindowSize);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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 org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class DefaultSpdyHeadersTest {
|
||||
|
||||
@Test
|
||||
public void testStringKeyRetrievedAsAsciiString() {
|
||||
final SpdyHeaders headers = new DefaultSpdyHeaders();
|
||||
|
||||
// Test adding String key and retrieving it using a AsciiString key
|
||||
final String method = "GET";
|
||||
headers.add(":method", method);
|
||||
|
||||
final String value = headers.getAsString(SpdyHeaders.HttpNames.METHOD.toString());
|
||||
assertNotNull(value);
|
||||
assertEquals(method, value);
|
||||
|
||||
final String value2 = headers.getAsString(SpdyHeaders.HttpNames.METHOD);
|
||||
assertNotNull(value2);
|
||||
assertEquals(method, value2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsciiStringKeyRetrievedAsString() {
|
||||
final SpdyHeaders headers = new DefaultSpdyHeaders();
|
||||
|
||||
// Test adding AsciiString key and retrieving it using a String key
|
||||
final String path = "/";
|
||||
headers.add(SpdyHeaders.HttpNames.PATH, path);
|
||||
|
||||
final String value = headers.getAsString(SpdyHeaders.HttpNames.PATH);
|
||||
assertNotNull(value);
|
||||
assertEquals(path, value);
|
||||
|
||||
final String value2 = headers.getAsString(SpdyHeaders.HttpNames.PATH.toString());
|
||||
assertNotNull(value2);
|
||||
assertEquals(path, value2);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,516 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SpdyHeaderBlockRawDecoderTest {
|
||||
|
||||
private static final int maxHeaderSize = 16;
|
||||
|
||||
private static final String name = "name";
|
||||
private static final String value = "value";
|
||||
private static final byte[] nameBytes = name.getBytes();
|
||||
private static final byte[] valueBytes = value.getBytes();
|
||||
|
||||
private SpdyHeaderBlockRawDecoder decoder;
|
||||
private SpdyHeadersFrame frame;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
decoder = new SpdyHeaderBlockRawDecoder(SpdyVersion.SPDY_3_1, maxHeaderSize);
|
||||
frame = new DefaultSpdyHeadersFrame(1);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
decoder.end();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyHeaderBlock() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.EMPTY_BUFFER;
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZeroNameValuePairs() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(4);
|
||||
headerBlock.writeInt(0);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeNameValuePairs() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(4);
|
||||
headerBlock.writeInt(-1);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneNameValuePair() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(21);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingNameLength() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(4);
|
||||
headerBlock.writeInt(1);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZeroNameLength() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(8);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(0);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeNameLength() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(8);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(-1);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingName() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(8);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalNameOnlyNull() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(18);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeByte(0);
|
||||
headerBlock.writeInt(5);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingValueLength() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(12);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZeroValueLength() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(16);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(0);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals("", frame.headers().get(name));
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeValueLength() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(16);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(-1);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingValue() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(16);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalValueOnlyNull() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(17);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeByte(0);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalValueStartsWithNull() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(22);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(6);
|
||||
headerBlock.writeByte(0);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalValueEndsWithNull() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(22);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(6);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleValues() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(27);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(11);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(2, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().getAll(name).get(0));
|
||||
assertEquals(value, frame.headers().getAll(name).get(1));
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleValuesEndsWithNull() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(28);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(12);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalValueMultipleNulls() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(28);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(12);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
headerBlock.writeByte(0);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingNextNameValuePair() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(21);
|
||||
headerBlock.writeInt(2);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleNames() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(38);
|
||||
headerBlock.writeInt(2);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtraData() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(22);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleDecodes() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(21);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
|
||||
int readableBytes = headerBlock.readableBytes();
|
||||
for (int i = 0; i < readableBytes; i++) {
|
||||
ByteBuf headerBlockSegment = headerBlock.slice(i, 1);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlockSegment, frame);
|
||||
assertFalse(headerBlockSegment.isReadable());
|
||||
}
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContinueAfterInvalidHeaders() throws Exception {
|
||||
ByteBuf numHeaders = Unpooled.buffer(4);
|
||||
numHeaders.writeInt(1);
|
||||
|
||||
ByteBuf nameBlock = Unpooled.buffer(8);
|
||||
nameBlock.writeInt(4);
|
||||
nameBlock.writeBytes(nameBytes);
|
||||
|
||||
ByteBuf valueBlock = Unpooled.buffer(9);
|
||||
valueBlock.writeInt(5);
|
||||
valueBlock.writeBytes(valueBytes);
|
||||
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, numHeaders, frame);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, nameBlock, frame);
|
||||
frame.setInvalid();
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, valueBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(numHeaders.isReadable());
|
||||
assertFalse(nameBlock.isReadable());
|
||||
assertFalse(valueBlock.isReadable());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
numHeaders.release();
|
||||
nameBlock.release();
|
||||
valueBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTruncatedHeaderName() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(maxHeaderSize + 18);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(maxHeaderSize + 1);
|
||||
for (int i = 0; i < maxHeaderSize + 1; i++) {
|
||||
headerBlock.writeByte('a');
|
||||
}
|
||||
headerBlock.writeInt(5);
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isTruncated());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTruncatedHeaderValue() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(maxHeaderSize + 13);
|
||||
headerBlock.writeInt(1);
|
||||
headerBlock.writeInt(4);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(13);
|
||||
for (int i = 0; i < maxHeaderSize - 3; i++) {
|
||||
headerBlock.writeByte('a');
|
||||
}
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertTrue(frame.isTruncated());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(0, frame.headers().names().size());
|
||||
headerBlock.release();
|
||||
}
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SpdyHeaderBlockZlibDecoderTest {
|
||||
|
||||
// zlib header indicating 32K window size fastest deflate algorithm with SPDY dictionary
|
||||
private static final byte[] zlibHeader = {0x78, 0x3f, (byte) 0xe3, (byte) 0xc6, (byte) 0xa7, (byte) 0xc2};
|
||||
private static final byte[] zlibSyncFlush = {0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff};
|
||||
|
||||
private static final int maxHeaderSize = 8192;
|
||||
|
||||
private static final String name = "name";
|
||||
private static final String value = "value";
|
||||
private static final byte[] nameBytes = name.getBytes();
|
||||
private static final byte[] valueBytes = value.getBytes();
|
||||
|
||||
private SpdyHeaderBlockZlibDecoder decoder;
|
||||
private SpdyHeadersFrame frame;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
decoder = new SpdyHeaderBlockZlibDecoder(SpdyVersion.SPDY_3_1, maxHeaderSize);
|
||||
frame = new DefaultSpdyHeadersFrame(1);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
decoder.end();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderBlock() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(37);
|
||||
headerBlock.writeBytes(zlibHeader);
|
||||
headerBlock.writeByte(0); // Non-compressed block
|
||||
headerBlock.writeByte(0x15); // little-endian length (21)
|
||||
headerBlock.writeByte(0x00); // little-endian length (21)
|
||||
headerBlock.writeByte(0xea); // one's compliment of length
|
||||
headerBlock.writeByte(0xff); // one's compliment of length
|
||||
headerBlock.writeInt(1); // number of Name/Value pairs
|
||||
headerBlock.writeInt(4); // length of name
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5); // length of value
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeBytes(zlibSyncFlush);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderBlockMultipleDecodes() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(37);
|
||||
headerBlock.writeBytes(zlibHeader);
|
||||
headerBlock.writeByte(0); // Non-compressed block
|
||||
headerBlock.writeByte(0x15); // little-endian length (21)
|
||||
headerBlock.writeByte(0x00); // little-endian length (21)
|
||||
headerBlock.writeByte(0xea); // one's compliment of length
|
||||
headerBlock.writeByte(0xff); // one's compliment of length
|
||||
headerBlock.writeInt(1); // number of Name/Value pairs
|
||||
headerBlock.writeInt(4); // length of name
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5); // length of value
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeBytes(zlibSyncFlush);
|
||||
|
||||
int readableBytes = headerBlock.readableBytes();
|
||||
for (int i = 0; i < readableBytes; i++) {
|
||||
ByteBuf headerBlockSegment = headerBlock.slice(i, 1);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlockSegment, frame);
|
||||
assertFalse(headerBlockSegment.isReadable());
|
||||
}
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(frame.isInvalid());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertTrue(frame.headers().contains(name));
|
||||
assertEquals(1, frame.headers().getAll(name).size());
|
||||
assertEquals(value, frame.headers().get(name));
|
||||
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargeHeaderName() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(8220);
|
||||
headerBlock.writeBytes(zlibHeader);
|
||||
headerBlock.writeByte(0); // Non-compressed block
|
||||
headerBlock.writeByte(0x0c); // little-endian length (8204)
|
||||
headerBlock.writeByte(0x20); // little-endian length (8204)
|
||||
headerBlock.writeByte(0xf3); // one's compliment of length
|
||||
headerBlock.writeByte(0xdf); // one's compliment of length
|
||||
headerBlock.writeInt(1); // number of Name/Value pairs
|
||||
headerBlock.writeInt(8192); // length of name
|
||||
for (int i = 0; i < 8192; i++) {
|
||||
headerBlock.writeByte('n');
|
||||
}
|
||||
headerBlock.writeInt(0); // length of value
|
||||
headerBlock.writeBytes(zlibSyncFlush);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertFalse(frame.isTruncated());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargeHeaderValue() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(8220);
|
||||
headerBlock.writeBytes(zlibHeader);
|
||||
headerBlock.writeByte(0); // Non-compressed block
|
||||
headerBlock.writeByte(0x0c); // little-endian length (8204)
|
||||
headerBlock.writeByte(0x20); // little-endian length (8204)
|
||||
headerBlock.writeByte(0xf3); // one's compliment of length
|
||||
headerBlock.writeByte(0xdf); // one's compliment of length
|
||||
headerBlock.writeInt(1); // number of Name/Value pairs
|
||||
headerBlock.writeInt(1); // length of name
|
||||
headerBlock.writeByte('n');
|
||||
headerBlock.writeInt(8191); // length of value
|
||||
for (int i = 0; i < 8191; i++) {
|
||||
headerBlock.writeByte('v');
|
||||
}
|
||||
headerBlock.writeBytes(zlibSyncFlush);
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
decoder.endHeaderBlock(frame);
|
||||
|
||||
assertFalse(headerBlock.isReadable());
|
||||
assertFalse(frame.isInvalid());
|
||||
assertFalse(frame.isTruncated());
|
||||
assertEquals(1, frame.headers().names().size());
|
||||
assertEquals(8191, frame.headers().get("n").length());
|
||||
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test(expected = SpdyProtocolException.class)
|
||||
public void testHeaderBlockExtraData() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(37);
|
||||
headerBlock.writeBytes(zlibHeader);
|
||||
headerBlock.writeByte(0); // Non-compressed block
|
||||
headerBlock.writeByte(0x15); // little-endian length (21)
|
||||
headerBlock.writeByte(0x00); // little-endian length (21)
|
||||
headerBlock.writeByte(0xea); // one's compliment of length
|
||||
headerBlock.writeByte(0xff); // one's compliment of length
|
||||
headerBlock.writeInt(1); // number of Name/Value pairs
|
||||
headerBlock.writeInt(4); // length of name
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
headerBlock.writeInt(5); // length of value
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0x19); // adler-32 checksum
|
||||
headerBlock.writeByte(0xa5); // adler-32 checksum
|
||||
headerBlock.writeByte(0x03); // adler-32 checksum
|
||||
headerBlock.writeByte(0xc9); // adler-32 checksum
|
||||
headerBlock.writeByte(0); // Data following zlib stream
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test(expected = SpdyProtocolException.class)
|
||||
public void testHeaderBlockInvalidDictionary() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(7);
|
||||
headerBlock.writeByte(0x78);
|
||||
headerBlock.writeByte(0x3f);
|
||||
headerBlock.writeByte(0x01); // Unknown dictionary
|
||||
headerBlock.writeByte(0x02); // Unknown dictionary
|
||||
headerBlock.writeByte(0x03); // Unknown dictionary
|
||||
headerBlock.writeByte(0x04); // Unknown dictionary
|
||||
headerBlock.writeByte(0); // Non-compressed block
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
headerBlock.release();
|
||||
}
|
||||
|
||||
@Test(expected = SpdyProtocolException.class)
|
||||
public void testHeaderBlockInvalidDeflateBlock() throws Exception {
|
||||
ByteBuf headerBlock = Unpooled.buffer(11);
|
||||
headerBlock.writeBytes(zlibHeader);
|
||||
headerBlock.writeByte(0); // Non-compressed block
|
||||
headerBlock.writeByte(0x00); // little-endian length (0)
|
||||
headerBlock.writeByte(0x00); // little-endian length (0)
|
||||
headerBlock.writeByte(0x00); // invalid one's compliment
|
||||
headerBlock.writeByte(0x00); // invalid one's compliment
|
||||
decoder.decode(ByteBufAllocator.DEFAULT, headerBlock, frame);
|
||||
|
||||
headerBlock.release();
|
||||
}
|
||||
}
|
@ -1,389 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SpdySessionHandlerTest {
|
||||
|
||||
private static final InternalLogger logger =
|
||||
InternalLoggerFactory.getInstance(SpdySessionHandlerTest.class);
|
||||
|
||||
private static final int closeSignal = SpdyCodecUtil.SPDY_SETTINGS_MAX_ID;
|
||||
private static final SpdySettingsFrame closeMessage = new DefaultSpdySettingsFrame();
|
||||
|
||||
static {
|
||||
closeMessage.setValue(closeSignal, 0);
|
||||
}
|
||||
|
||||
private static void assertDataFrame(Object msg, int streamId, boolean last) {
|
||||
assertNotNull(msg);
|
||||
assertTrue(msg instanceof SpdyDataFrame);
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
assertEquals(streamId, spdyDataFrame.streamId());
|
||||
assertEquals(last, spdyDataFrame.isLast());
|
||||
}
|
||||
|
||||
private static void assertSynReply(Object msg, int streamId, boolean last, SpdyHeaders headers) {
|
||||
assertNotNull(msg);
|
||||
assertTrue(msg instanceof SpdySynReplyFrame);
|
||||
assertHeaders(msg, streamId, last, headers);
|
||||
}
|
||||
|
||||
private static void assertRstStream(Object msg, int streamId, SpdyStreamStatus status) {
|
||||
assertNotNull(msg);
|
||||
assertTrue(msg instanceof SpdyRstStreamFrame);
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
assertEquals(streamId, spdyRstStreamFrame.streamId());
|
||||
assertEquals(status, spdyRstStreamFrame.status());
|
||||
}
|
||||
|
||||
private static void assertPing(Object msg, int id) {
|
||||
assertNotNull(msg);
|
||||
assertTrue(msg instanceof SpdyPingFrame);
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
assertEquals(id, spdyPingFrame.id());
|
||||
}
|
||||
|
||||
private static void assertGoAway(Object msg, int lastGoodStreamId) {
|
||||
assertNotNull(msg);
|
||||
assertTrue(msg instanceof SpdyGoAwayFrame);
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
|
||||
assertEquals(lastGoodStreamId, spdyGoAwayFrame.lastGoodStreamId());
|
||||
}
|
||||
|
||||
private static void assertHeaders(Object msg, int streamId, boolean last, SpdyHeaders headers) {
|
||||
assertNotNull(msg);
|
||||
assertTrue(msg instanceof SpdyHeadersFrame);
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
assertEquals(streamId, spdyHeadersFrame.streamId());
|
||||
assertEquals(last, spdyHeadersFrame.isLast());
|
||||
for (CharSequence name: headers.names()) {
|
||||
List<CharSequence> expectedValues = headers.getAll(name);
|
||||
List<CharSequence> receivedValues = spdyHeadersFrame.headers().getAll(name);
|
||||
assertTrue(receivedValues.containsAll(expectedValues));
|
||||
receivedValues.removeAll(expectedValues);
|
||||
assertTrue(receivedValues.isEmpty());
|
||||
spdyHeadersFrame.headers().remove(name);
|
||||
}
|
||||
assertTrue(spdyHeadersFrame.headers().isEmpty());
|
||||
}
|
||||
|
||||
private static void testSpdySessionHandler(SpdyVersion version, boolean server) {
|
||||
EmbeddedChannel sessionHandler = new EmbeddedChannel(
|
||||
new SpdySessionHandler(version, server), new EchoHandler(closeSignal, server));
|
||||
|
||||
while (sessionHandler.readOutbound() != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int localStreamId = server ? 1 : 2;
|
||||
int remoteStreamId = server ? 2 : 1;
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0);
|
||||
spdySynStreamFrame.headers().set("compression", "test");
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId);
|
||||
spdyDataFrame.setLast(true);
|
||||
|
||||
// Check if session handler returns INVALID_STREAM if it receives
|
||||
// a data frame for a Stream-ID that is not open
|
||||
sessionHandler.writeInbound(new DefaultSpdyDataFrame(localStreamId));
|
||||
assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.INVALID_STREAM);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
// Check if session handler returns PROTOCOL_ERROR if it receives
|
||||
// a data frame for a Stream-ID before receiving a SYN_REPLY frame
|
||||
sessionHandler.writeInbound(new DefaultSpdyDataFrame(remoteStreamId));
|
||||
assertRstStream(sessionHandler.readOutbound(), remoteStreamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
remoteStreamId += 2;
|
||||
|
||||
// Check if session handler returns PROTOCOL_ERROR if it receives
|
||||
// multiple SYN_REPLY frames for the same active Stream-ID
|
||||
sessionHandler.writeInbound(new DefaultSpdySynReplyFrame(remoteStreamId));
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
sessionHandler.writeInbound(new DefaultSpdySynReplyFrame(remoteStreamId));
|
||||
assertRstStream(sessionHandler.readOutbound(), remoteStreamId, SpdyStreamStatus.STREAM_IN_USE);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
remoteStreamId += 2;
|
||||
|
||||
// Check if frame codec correctly compresses/uncompresses headers
|
||||
sessionHandler.writeInbound(spdySynStreamFrame);
|
||||
assertSynReply(sessionHandler.readOutbound(), localStreamId, false, spdySynStreamFrame.headers());
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(localStreamId);
|
||||
|
||||
spdyHeadersFrame.headers().add("header", "test1");
|
||||
spdyHeadersFrame.headers().add("header", "test2");
|
||||
|
||||
sessionHandler.writeInbound(spdyHeadersFrame);
|
||||
assertHeaders(sessionHandler.readOutbound(), localStreamId, false, spdyHeadersFrame.headers());
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
localStreamId += 2;
|
||||
|
||||
// Check if session handler closed the streams using the number
|
||||
// of concurrent streams and that it returns REFUSED_STREAM
|
||||
// if it receives a SYN_STREAM frame it does not wish to accept
|
||||
spdySynStreamFrame.setStreamId(localStreamId);
|
||||
spdySynStreamFrame.setLast(true);
|
||||
spdySynStreamFrame.setUnidirectional(true);
|
||||
|
||||
sessionHandler.writeInbound(spdySynStreamFrame);
|
||||
assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.REFUSED_STREAM);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
// Check if session handler rejects HEADERS for closed streams
|
||||
int testStreamId = spdyDataFrame.streamId();
|
||||
sessionHandler.writeInbound(spdyDataFrame);
|
||||
assertDataFrame(sessionHandler.readOutbound(), testStreamId, spdyDataFrame.isLast());
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
spdyHeadersFrame.setStreamId(testStreamId);
|
||||
|
||||
sessionHandler.writeInbound(spdyHeadersFrame);
|
||||
assertRstStream(sessionHandler.readOutbound(), testStreamId, SpdyStreamStatus.INVALID_STREAM);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
// Check if session handler drops active streams if it receives
|
||||
// a RST_STREAM frame for that Stream-ID
|
||||
sessionHandler.writeInbound(new DefaultSpdyRstStreamFrame(remoteStreamId, 3));
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
//remoteStreamId += 2;
|
||||
|
||||
// Check if session handler honors UNIDIRECTIONAL streams
|
||||
spdySynStreamFrame.setLast(false);
|
||||
sessionHandler.writeInbound(spdySynStreamFrame);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
spdySynStreamFrame.setUnidirectional(false);
|
||||
|
||||
// Check if session handler returns PROTOCOL_ERROR if it receives
|
||||
// multiple SYN_STREAM frames for the same active Stream-ID
|
||||
sessionHandler.writeInbound(spdySynStreamFrame);
|
||||
assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
localStreamId += 2;
|
||||
|
||||
// Check if session handler returns PROTOCOL_ERROR if it receives
|
||||
// a SYN_STREAM frame with an invalid Stream-ID
|
||||
spdySynStreamFrame.setStreamId(localStreamId - 1);
|
||||
sessionHandler.writeInbound(spdySynStreamFrame);
|
||||
assertRstStream(sessionHandler.readOutbound(), localStreamId - 1, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
spdySynStreamFrame.setStreamId(localStreamId);
|
||||
|
||||
// Check if session handler returns PROTOCOL_ERROR if it receives
|
||||
// an invalid HEADERS frame
|
||||
spdyHeadersFrame.setStreamId(localStreamId);
|
||||
|
||||
spdyHeadersFrame.setInvalid();
|
||||
sessionHandler.writeInbound(spdyHeadersFrame);
|
||||
assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
sessionHandler.finish();
|
||||
}
|
||||
|
||||
private static void testSpdySessionHandlerPing(SpdyVersion version, boolean server) {
|
||||
EmbeddedChannel sessionHandler = new EmbeddedChannel(
|
||||
new SpdySessionHandler(version, server), new EchoHandler(closeSignal, server));
|
||||
|
||||
while (sessionHandler.readOutbound() != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int localStreamId = server ? 1 : 2;
|
||||
int remoteStreamId = server ? 2 : 1;
|
||||
|
||||
SpdyPingFrame localPingFrame = new DefaultSpdyPingFrame(localStreamId);
|
||||
SpdyPingFrame remotePingFrame = new DefaultSpdyPingFrame(remoteStreamId);
|
||||
|
||||
// Check if session handler returns identical local PINGs
|
||||
sessionHandler.writeInbound(localPingFrame);
|
||||
assertPing(sessionHandler.readOutbound(), localPingFrame.id());
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
// Check if session handler ignores un-initiated remote PINGs
|
||||
sessionHandler.writeInbound(remotePingFrame);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
sessionHandler.finish();
|
||||
}
|
||||
|
||||
private static void testSpdySessionHandlerGoAway(SpdyVersion version, boolean server) {
|
||||
EmbeddedChannel sessionHandler = new EmbeddedChannel(
|
||||
new SpdySessionHandler(version, server), new EchoHandler(closeSignal, server));
|
||||
|
||||
while (sessionHandler.readOutbound() != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int localStreamId = server ? 1 : 2;
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0);
|
||||
spdySynStreamFrame.headers().set("compression", "test");
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId);
|
||||
spdyDataFrame.setLast(true);
|
||||
|
||||
// Send an initial request
|
||||
sessionHandler.writeInbound(spdySynStreamFrame);
|
||||
assertSynReply(sessionHandler.readOutbound(), localStreamId, false, spdySynStreamFrame.headers());
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
sessionHandler.writeInbound(spdyDataFrame);
|
||||
assertDataFrame(sessionHandler.readOutbound(), localStreamId, true);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
// Check if session handler sends a GOAWAY frame when closing
|
||||
sessionHandler.writeInbound(closeMessage);
|
||||
assertGoAway(sessionHandler.readOutbound(), localStreamId);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
localStreamId += 2;
|
||||
|
||||
// Check if session handler returns REFUSED_STREAM if it receives
|
||||
// SYN_STREAM frames after sending a GOAWAY frame
|
||||
spdySynStreamFrame.setStreamId(localStreamId);
|
||||
sessionHandler.writeInbound(spdySynStreamFrame);
|
||||
assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.REFUSED_STREAM);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
// Check if session handler ignores Data frames after sending
|
||||
// a GOAWAY frame
|
||||
spdyDataFrame.setStreamId(localStreamId);
|
||||
sessionHandler.writeInbound(spdyDataFrame);
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
|
||||
sessionHandler.finish();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpdyClientSessionHandler() {
|
||||
logger.info("Running: testSpdyClientSessionHandler v3.1");
|
||||
testSpdySessionHandler(SpdyVersion.SPDY_3_1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpdyClientSessionHandlerPing() {
|
||||
logger.info("Running: testSpdyClientSessionHandlerPing v3.1");
|
||||
testSpdySessionHandlerPing(SpdyVersion.SPDY_3_1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpdyClientSessionHandlerGoAway() {
|
||||
logger.info("Running: testSpdyClientSessionHandlerGoAway v3.1");
|
||||
testSpdySessionHandlerGoAway(SpdyVersion.SPDY_3_1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpdyServerSessionHandler() {
|
||||
logger.info("Running: testSpdyServerSessionHandler v3.1");
|
||||
testSpdySessionHandler(SpdyVersion.SPDY_3_1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpdyServerSessionHandlerPing() {
|
||||
logger.info("Running: testSpdyServerSessionHandlerPing v3.1");
|
||||
testSpdySessionHandlerPing(SpdyVersion.SPDY_3_1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpdyServerSessionHandlerGoAway() {
|
||||
logger.info("Running: testSpdyServerSessionHandlerGoAway v3.1");
|
||||
testSpdySessionHandlerGoAway(SpdyVersion.SPDY_3_1, true);
|
||||
}
|
||||
|
||||
// Echo Handler opens 4 half-closed streams on session connection
|
||||
// and then sets the number of concurrent streams to 1
|
||||
private static class EchoHandler extends ChannelInboundHandlerAdapter {
|
||||
private final int closeSignal;
|
||||
private final boolean server;
|
||||
|
||||
EchoHandler(int closeSignal, boolean server) {
|
||||
this.closeSignal = closeSignal;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
// Initiate 4 new streams
|
||||
int streamId = server ? 2 : 1;
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamId, 0, (byte) 0);
|
||||
spdySynStreamFrame.setLast(true);
|
||||
ctx.writeAndFlush(spdySynStreamFrame);
|
||||
spdySynStreamFrame.setStreamId(spdySynStreamFrame.streamId() + 2);
|
||||
ctx.writeAndFlush(spdySynStreamFrame);
|
||||
spdySynStreamFrame.setStreamId(spdySynStreamFrame.streamId() + 2);
|
||||
ctx.writeAndFlush(spdySynStreamFrame);
|
||||
spdySynStreamFrame.setStreamId(spdySynStreamFrame.streamId() + 2);
|
||||
ctx.writeAndFlush(spdySynStreamFrame);
|
||||
|
||||
// Limit the number of concurrent streams to 1
|
||||
SpdySettingsFrame spdySettingsFrame = new DefaultSpdySettingsFrame();
|
||||
spdySettingsFrame.setValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS, 1);
|
||||
ctx.writeAndFlush(spdySettingsFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
if (!spdySynStreamFrame.isUnidirectional()) {
|
||||
int streamId = spdySynStreamFrame.streamId();
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||
spdySynReplyFrame.setLast(spdySynStreamFrame.isLast());
|
||||
for (Map.Entry<CharSequence, CharSequence> entry: spdySynStreamFrame.headers()) {
|
||||
spdySynReplyFrame.headers().add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
ctx.writeAndFlush(spdySynReplyFrame);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg instanceof SpdySynReplyFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg instanceof SpdyDataFrame ||
|
||||
msg instanceof SpdyPingFrame ||
|
||||
msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
ctx.writeAndFlush(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg instanceof SpdySettingsFrame) {
|
||||
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
|
||||
if (spdySettingsFrame.isSet(closeSignal)) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.client;
|
||||
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.example.http.snoop.HttpSnoopClientHandler;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* This is a modified version of {@link HttpSnoopClientHandler} that uses a {@link BlockingQueue} to wait until an
|
||||
* HTTPResponse is received.
|
||||
*/
|
||||
public class HttpResponseClientHandler extends SimpleChannelInboundHandler<HttpObject> {
|
||||
|
||||
private final BlockingQueue<ChannelFuture> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
|
||||
if (msg instanceof HttpResponse) {
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
|
||||
System.out.println("STATUS: " + response.status());
|
||||
System.out.println("VERSION: " + response.protocolVersion());
|
||||
System.out.println();
|
||||
|
||||
if (!response.headers().isEmpty()) {
|
||||
for (CharSequence name : response.headers().names()) {
|
||||
for (CharSequence value : response.headers().getAll(name)) {
|
||||
System.out.println("HEADER: " + name + " = " + value);
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
if (HttpUtil.isTransferEncodingChunked(response)) {
|
||||
System.out.println("CHUNKED CONTENT {");
|
||||
} else {
|
||||
System.out.println("CONTENT {");
|
||||
}
|
||||
}
|
||||
if (msg instanceof HttpContent) {
|
||||
HttpContent content = (HttpContent) msg;
|
||||
|
||||
System.out.print(content.content().toString(CharsetUtil.UTF_8));
|
||||
System.out.flush();
|
||||
|
||||
if (content instanceof LastHttpContent) {
|
||||
System.out.println("} END OF CONTENT");
|
||||
queue.add(ctx.channel().newSucceededFuture());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
queue.add(ctx.channel().newFailedFuture(cause));
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
public BlockingQueue<ChannelFuture> queue() {
|
||||
return queue;
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.client;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.MultithreadEventLoopGroup;
|
||||
import io.netty.channel.nio.NioHandler;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNames;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
|
||||
/**
|
||||
* An SPDY client that allows you to send HTTP GET to a SPDY server.
|
||||
* <p>
|
||||
* This class must be run with the JVM parameter: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}. The
|
||||
* "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from Maven at
|
||||
* coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. See
|
||||
* <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty docs</a> for more information.
|
||||
* <p>
|
||||
* You may also use the {@code run-example.sh} script to start the client from the command line:
|
||||
* <pre>
|
||||
* ./run-example.sh spdy-client
|
||||
* </pre>
|
||||
*/
|
||||
public final class SpdyClient {
|
||||
|
||||
static final String HOST = System.getProperty("host", "127.0.0.1");
|
||||
static final int PORT = Integer.parseInt(System.getProperty("port", "8443"));
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Configure SSL.
|
||||
final SslContext sslCtx = SslContextBuilder.forClient()
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.applicationProtocolConfig(new ApplicationProtocolConfig(
|
||||
Protocol.NPN,
|
||||
// NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.
|
||||
SelectorFailureBehavior.NO_ADVERTISE,
|
||||
// ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.
|
||||
SelectedListenerFailureBehavior.ACCEPT,
|
||||
ApplicationProtocolNames.SPDY_3_1,
|
||||
ApplicationProtocolNames.HTTP_1_1))
|
||||
.build();
|
||||
|
||||
HttpResponseClientHandler httpResponseHandler = new HttpResponseClientHandler();
|
||||
EventLoopGroup workerGroup = new MultithreadEventLoopGroup(NioHandler.newFactory());
|
||||
|
||||
try {
|
||||
Bootstrap b = new Bootstrap();
|
||||
b.group(workerGroup);
|
||||
b.channel(NioSocketChannel.class);
|
||||
b.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
b.remoteAddress(HOST, PORT);
|
||||
b.handler(new SpdyClientInitializer(sslCtx, httpResponseHandler));
|
||||
|
||||
// Start the client.
|
||||
Channel channel = b.connect().syncUninterruptibly().channel();
|
||||
System.out.println("Connected to " + HOST + ':' + PORT);
|
||||
|
||||
// Create a GET request.
|
||||
HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "");
|
||||
request.headers().set(HttpHeaderNames.HOST, HOST);
|
||||
request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);
|
||||
|
||||
// Send the GET request.
|
||||
channel.writeAndFlush(request).sync();
|
||||
|
||||
// Waits for the complete HTTP response
|
||||
httpResponseHandler.queue().take().sync();
|
||||
System.out.println("Finished SPDY HTTP GET");
|
||||
|
||||
// Wait until the connection is closed.
|
||||
channel.close().syncUninterruptibly();
|
||||
} finally {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.client;
|
||||
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.spdy.SpdyFrameCodec;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpDecoder;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpEncoder;
|
||||
import io.netty.handler.codec.spdy.SpdySessionHandler;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyVersion.*;
|
||||
import static io.netty.util.internal.logging.InternalLogLevel.*;
|
||||
|
||||
public class SpdyClientInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
private static final int MAX_SPDY_CONTENT_LENGTH = 1024 * 1024; // 1 MB
|
||||
|
||||
private final SslContext sslCtx;
|
||||
private final HttpResponseClientHandler httpResponseHandler;
|
||||
|
||||
public SpdyClientInitializer(SslContext sslCtx, HttpResponseClientHandler httpResponseHandler) {
|
||||
this.sslCtx = sslCtx;
|
||||
this.httpResponseHandler = httpResponseHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc()));
|
||||
pipeline.addLast("spdyFrameCodec", new SpdyFrameCodec(SPDY_3_1));
|
||||
pipeline.addLast("spdyFrameLogger", new SpdyFrameLogger(INFO));
|
||||
pipeline.addLast("spdySessionHandler", new SpdySessionHandler(SPDY_3_1, false));
|
||||
pipeline.addLast("spdyHttpEncoder", new SpdyHttpEncoder(SPDY_3_1));
|
||||
pipeline.addLast("spdyHttpDecoder", new SpdyHttpDecoder(SPDY_3_1, MAX_SPDY_CONTENT_LENGTH));
|
||||
pipeline.addLast("spdyStreamIdHandler", new SpdyClientStreamIdHandler());
|
||||
pipeline.addLast("httpHandler", httpResponseHandler);
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.client;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||
|
||||
/**
|
||||
* Adds a unique client stream ID to the SPDY header. Client stream IDs MUST be odd.
|
||||
*/
|
||||
public class SpdyClientStreamIdHandler extends ChannelOutboundHandlerAdapter {
|
||||
|
||||
private int currentStreamId = 1;
|
||||
|
||||
public boolean acceptOutboundMessage(Object msg) {
|
||||
return msg instanceof HttpMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
||||
if (acceptOutboundMessage(msg)) {
|
||||
HttpMessage httpMsg = (HttpMessage) msg;
|
||||
if (!httpMsg.headers().contains(SpdyHttpHeaders.Names.STREAM_ID)) {
|
||||
httpMsg.headers().setInt(Names.STREAM_ID, currentStreamId);
|
||||
// Client stream IDs are always odd
|
||||
currentStreamId += 2;
|
||||
}
|
||||
}
|
||||
ctx.write(msg, promise);
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.client;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.spdy.SpdyFrame;
|
||||
import io.netty.util.internal.logging.InternalLogLevel;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* Logs SPDY frames for debugging purposes.
|
||||
*/
|
||||
public class SpdyFrameLogger extends ChannelDuplexHandler {
|
||||
|
||||
private enum Direction {
|
||||
INBOUND, OUTBOUND
|
||||
}
|
||||
|
||||
protected final InternalLogger logger;
|
||||
private final InternalLogLevel level;
|
||||
|
||||
public SpdyFrameLogger(InternalLogLevel level) {
|
||||
if (level == null) {
|
||||
throw new NullPointerException("level");
|
||||
}
|
||||
|
||||
logger = InternalLoggerFactory.getInstance(getClass());
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (acceptMessage(msg)) {
|
||||
log((SpdyFrame) msg, Direction.INBOUND);
|
||||
}
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
||||
if (acceptMessage(msg)) {
|
||||
log((SpdyFrame) msg, Direction.OUTBOUND);
|
||||
}
|
||||
ctx.write(msg, promise);
|
||||
}
|
||||
|
||||
private static boolean acceptMessage(Object msg) {
|
||||
return msg instanceof SpdyFrame;
|
||||
}
|
||||
|
||||
private void log(SpdyFrame msg, Direction d) {
|
||||
if (logger.isEnabled(level)) {
|
||||
StringBuilder b = new StringBuilder(200)
|
||||
.append("\n----------------")
|
||||
.append(d.name())
|
||||
.append("--------------------\n")
|
||||
.append(msg)
|
||||
.append("\n------------------------------------");
|
||||
|
||||
logger.log(level, b.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package contains an example SPDY HTTP client. It will behave like a SPDY-enabled browser and you can see the
|
||||
* SPDY frames flowing in and out using the {@link io.netty.example.spdy.client.SpdyFrameLogger}.
|
||||
*
|
||||
* <p>
|
||||
* This package relies on the Jetty project's implementation of the Transport Layer Security (TLS) extension for Next
|
||||
* Protocol Negotiation (NPN) for OpenJDK 7 is required. NPN allows the application layer to negotiate which
|
||||
* protocol, SPDY or HTTP, to use.
|
||||
* <p>
|
||||
* To start, run {@link io.netty.example.spdy.server.SpdyServer} with the JVM parameter:
|
||||
* {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}.
|
||||
* The "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from
|
||||
* Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions.
|
||||
* See <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty docs</a> for more
|
||||
* information.
|
||||
* <p>
|
||||
* After that, you can run {@link io.netty.example.spdy.client.SpdyClient}, also settings the JVM parameter
|
||||
* mentioned above.
|
||||
* <p>
|
||||
* You may also use the {@code run-example.sh} script to start the server and the client from the command line:
|
||||
* <pre>
|
||||
* ./run-example spdy-server
|
||||
* </pre>
|
||||
* Then start the client in a different terminal window:
|
||||
* <pre>
|
||||
* ./run-example spdy-client
|
||||
* </pre>
|
||||
*/
|
||||
package io.netty.example.spdy.client;
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.spdy.SpdyFrameCodec;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpDecoder;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpEncoder;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpResponseStreamIdHandler;
|
||||
import io.netty.handler.codec.spdy.SpdySessionHandler;
|
||||
import io.netty.handler.codec.spdy.SpdyVersion;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNames;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
|
||||
|
||||
/**
|
||||
* Negotiates with the browser if SPDY or HTTP is going to be used. Once decided, the Netty pipeline is setup with
|
||||
* the correct handlers for the selected protocol.
|
||||
*/
|
||||
public class SpdyOrHttpHandler extends ApplicationProtocolNegotiationHandler {
|
||||
|
||||
private static final int MAX_CONTENT_LENGTH = 1024 * 100;
|
||||
|
||||
protected SpdyOrHttpHandler() {
|
||||
super(ApplicationProtocolNames.HTTP_1_1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {
|
||||
if (ApplicationProtocolNames.SPDY_3_1.equals(protocol)) {
|
||||
configureSpdy(ctx, SpdyVersion.SPDY_3_1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
|
||||
configureHttp1(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("unknown protocol: " + protocol);
|
||||
}
|
||||
|
||||
private static void configureSpdy(ChannelHandlerContext ctx, SpdyVersion version) throws Exception {
|
||||
ChannelPipeline p = ctx.pipeline();
|
||||
p.addLast(new SpdyFrameCodec(version));
|
||||
p.addLast(new SpdySessionHandler(version, true));
|
||||
p.addLast(new SpdyHttpEncoder(version));
|
||||
p.addLast(new SpdyHttpDecoder(version, MAX_CONTENT_LENGTH));
|
||||
p.addLast(new SpdyHttpResponseStreamIdHandler());
|
||||
p.addLast(new SpdyServerHandler());
|
||||
}
|
||||
|
||||
private static void configureHttp1(ChannelHandlerContext ctx) throws Exception {
|
||||
ChannelPipeline p = ctx.pipeline();
|
||||
p.addLast(new HttpServerCodec());
|
||||
p.addLast(new HttpObjectAggregator(MAX_CONTENT_LENGTH));
|
||||
p.addLast(new SpdyServerHandler());
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.server;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.MultithreadEventLoopGroup;
|
||||
import io.netty.channel.nio.NioHandler;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNames;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
|
||||
/**
|
||||
* A SPDY Server that responds to a GET request with a Hello World.
|
||||
* <p>
|
||||
* This class must be run with the JVM parameter: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}.
|
||||
* The "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from
|
||||
* Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions.
|
||||
* See <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty docs</a> for more
|
||||
* information.
|
||||
* <p>
|
||||
* You may also use the {@code run-example.sh} script to start the server from the command line:
|
||||
* <pre>
|
||||
* ./run-example.sh spdy-server
|
||||
* </pre>
|
||||
* <p>
|
||||
* Once started, you can test the server with your
|
||||
* <a href="http://en.wikipedia.org/wiki/SPDY#Browser_support_and_usage">SPDY enabled web browser</a> by navigating
|
||||
* to <a href="https://localhost:8443/">https://localhost:8443/</a>
|
||||
*/
|
||||
public final class SpdyServer {
|
||||
|
||||
static final int PORT = Integer.parseInt(System.getProperty("port", "8443"));
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Configure SSL.
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.applicationProtocolConfig(new ApplicationProtocolConfig(
|
||||
Protocol.NPN,
|
||||
// NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.
|
||||
SelectorFailureBehavior.NO_ADVERTISE,
|
||||
// ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.
|
||||
SelectedListenerFailureBehavior.ACCEPT,
|
||||
ApplicationProtocolNames.SPDY_3_1,
|
||||
ApplicationProtocolNames.HTTP_1_1))
|
||||
.build();
|
||||
|
||||
// Configure the server.
|
||||
EventLoopGroup bossGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
|
||||
EventLoopGroup workerGroup = new MultithreadEventLoopGroup(NioHandler.newFactory());
|
||||
try {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.option(ChannelOption.SO_BACKLOG, 1024);
|
||||
b.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler(LogLevel.INFO))
|
||||
.childHandler(new SpdyServerInitializer(sslCtx));
|
||||
|
||||
Channel ch = b.bind(PORT).sync().channel();
|
||||
|
||||
System.err.println("Open your SPDY-enabled web browser and navigate to https://127.0.0.1:" + PORT + '/');
|
||||
System.err.println("If using Chrome browser, check your SPDY sessions at chrome://net-internals/#spdy");
|
||||
|
||||
ch.closeFuture().sync();
|
||||
} finally {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.server;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpUtil.*;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
|
||||
/**
|
||||
* HTTP handler that responds with a "Hello World"
|
||||
*/
|
||||
public class SpdyServerHandler extends SimpleChannelInboundHandler<Object> {
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof HttpRequest) {
|
||||
HttpRequest req = (HttpRequest) msg;
|
||||
|
||||
if (is100ContinueExpected(req)) {
|
||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
||||
}
|
||||
boolean keepAlive = isKeepAlive(req);
|
||||
|
||||
ByteBuf content = Unpooled.copiedBuffer("Hello World " + new Date(), CharsetUtil.UTF_8);
|
||||
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
|
||||
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
|
||||
|
||||
if (!keepAlive) {
|
||||
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
|
||||
ctx.write(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.spdy.server;
|
||||
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
|
||||
/**
|
||||
* Sets up the Netty pipeline
|
||||
*/
|
||||
public class SpdyServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
private final SslContext sslCtx;
|
||||
|
||||
public SpdyServerInitializer(SslContext sslCtx) {
|
||||
this.sslCtx = sslCtx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) {
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
p.addLast(sslCtx.newHandler(ch.alloc()));
|
||||
// Negotiates with the browser if SPDY or HTTP is going to be used
|
||||
p.addLast(new SpdyOrHttpHandler());
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package contains an example SPDY HTTP web server.
|
||||
* <p>
|
||||
* This package relies on the Jetty project's implementation of the Transport Layer Security (TLS) extension for Next
|
||||
* Protocol Negotiation (NPN) for OpenJDK 7 is required. NPN allows the application layer to negotiate which
|
||||
* protocol, SPDY or HTTP, to use.
|
||||
* <p>
|
||||
* To start, run {@link io.netty.example.spdy.server.SpdyServer} with the JVM parameter:
|
||||
* {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}.
|
||||
* The "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from
|
||||
* Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions.
|
||||
* See <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty docs</a> for more
|
||||
* information.
|
||||
* <p>
|
||||
* You may also use the {@code run-example.sh} script to start the server from the command line:
|
||||
* <pre>
|
||||
* ./run-example spdy-server
|
||||
* </pre>
|
||||
* <p>
|
||||
* Once started, you can test the server with your
|
||||
* <a href="http://en.wikipedia.org/wiki/SPDY#Browser_support_and_usage">SPDY enabled web browser</a> by navigating
|
||||
* to <a href="https://localhost:8443/">https://localhost:8443/</a>
|
||||
*/
|
||||
package io.netty.example.spdy.server;
|
@ -35,25 +35,5 @@ public final class ApplicationProtocolNames {
|
||||
*/
|
||||
public static final String HTTP_1_1 = "http/1.1";
|
||||
|
||||
/**
|
||||
* {@code "spdy/3.1"}: SPDY version 3.1
|
||||
*/
|
||||
public static final String SPDY_3_1 = "spdy/3.1";
|
||||
|
||||
/**
|
||||
* {@code "spdy/3"}: SPDY version 3
|
||||
*/
|
||||
public static final String SPDY_3 = "spdy/3";
|
||||
|
||||
/**
|
||||
* {@code "spdy/2"}: SPDY version 2
|
||||
*/
|
||||
public static final String SPDY_2 = "spdy/2";
|
||||
|
||||
/**
|
||||
* {@code "spdy/1"}: SPDY version 1
|
||||
*/
|
||||
public static final String SPDY_1 = "spdy/1";
|
||||
|
||||
private ApplicationProtocolNames() { }
|
||||
}
|
||||
|
@ -20,8 +20,6 @@ EXAMPLE_MAP=(
|
||||
'http2-server:io.netty.example.http2.helloworld.server.Http2Server'
|
||||
'http2-tiles:io.netty.example.http2.tiles.Launcher'
|
||||
'http2-multiplex-server:io.netty.example.http2.helloworld.multiplex.server.Http2Server'
|
||||
'spdy-client:io.netty.example.spdy.client.SpdyClient'
|
||||
'spdy-server:io.netty.example.spdy.server.SpdyServer'
|
||||
'worldclock-client:io.netty.example.worldclock.WorldClockClient'
|
||||
'worldclock-server:io.netty.example.worldclock.WorldClockServer'
|
||||
'objectecho-client:io.netty.example.objectecho.ObjectEchoClient'
|
||||
@ -44,11 +42,6 @@ EXAMPLE_MAP=(
|
||||
'localecho:io.netty.example.localecho.LocalEcho'
|
||||
)
|
||||
|
||||
NEEDS_NPN_MAP=(
|
||||
'spdy-client'
|
||||
'spdy-server'
|
||||
)
|
||||
|
||||
EXAMPLE=''
|
||||
EXAMPLE_CLASS=''
|
||||
EXAMPLE_ARGS='-D_'
|
||||
@ -103,13 +96,6 @@ if [[ -z "$EXAMPLE" ]] || [[ -z "$EXAMPLE_CLASS" ]] || [[ $# -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for E in "${NEEDS_NPN_MAP[@]}"; do
|
||||
if [[ "$EXAMPLE" = "$E" ]]; then
|
||||
FORCE_NPN='true'
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
cd "`dirname "$0"`"/example
|
||||
echo "[INFO] Running: $EXAMPLE ($EXAMPLE_CLASS $EXAMPLE_ARGS)"
|
||||
exec mvn -q -nsu compile exec:exec -Dcheckstyle.skip=true -Dforbiddenapis.skip=true -Dforcenpn="$FORCE_NPN" -DargLine.example="$EXAMPLE_ARGS" -DexampleClass="$EXAMPLE_CLASS"
|
||||
|
@ -1,312 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.testsuite.transport.socket;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.spdy.SpdyFrameCodec;
|
||||
import io.netty.handler.codec.spdy.SpdyVersion;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SocketSpdyEchoTest extends AbstractSocketTest {
|
||||
|
||||
private static final Random random = new Random();
|
||||
static final int ignoredBytes = 20;
|
||||
|
||||
private static ByteBuf createFrames(int version) {
|
||||
ByteBuf frames = Unpooled.buffer(1174);
|
||||
|
||||
// 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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
frames.writeInt(8);
|
||||
frames.writeInt(random.nextInt() & 0x7FFFFFFF);
|
||||
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;
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void testSpdyEcho() throws Throwable {
|
||||
run();
|
||||
}
|
||||
|
||||
public void testSpdyEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable {
|
||||
logger.info("Testing against SPDY v3.1");
|
||||
testSpdyEcho(sb, cb, SpdyVersion.SPDY_3_1, true);
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void testSpdyEchoNotAutoRead() throws Throwable {
|
||||
run();
|
||||
}
|
||||
|
||||
public void testSpdyEchoNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable {
|
||||
logger.info("Testing against SPDY v3.1");
|
||||
testSpdyEcho(sb, cb, SpdyVersion.SPDY_3_1, false);
|
||||
}
|
||||
|
||||
private static void testSpdyEcho(
|
||||
ServerBootstrap sb, Bootstrap cb, final SpdyVersion version, boolean autoRead) throws Throwable {
|
||||
|
||||
ByteBuf frames;
|
||||
switch (version) {
|
||||
case SPDY_3_1:
|
||||
frames = createFrames(3);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown version");
|
||||
}
|
||||
|
||||
sb.childOption(ChannelOption.AUTO_READ, autoRead);
|
||||
cb.option(ChannelOption.AUTO_READ, autoRead);
|
||||
|
||||
final SpdyEchoTestServerHandler sh = new SpdyEchoTestServerHandler(autoRead);
|
||||
final SpdyEchoTestClientHandler ch = new SpdyEchoTestClientHandler(frames.copy(), autoRead);
|
||||
|
||||
sb.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel channel) throws Exception {
|
||||
channel.pipeline().addLast(
|
||||
new SpdyFrameCodec(version),
|
||||
sh);
|
||||
}
|
||||
});
|
||||
|
||||
cb.handler(ch);
|
||||
|
||||
Channel sc = sb.bind().sync().channel();
|
||||
|
||||
Channel cc = cb.connect(sc.localAddress()).sync().channel();
|
||||
cc.writeAndFlush(frames);
|
||||
|
||||
while (ch.counter < frames.writerIndex() - ignoredBytes) {
|
||||
if (sh.exception.get() != null) {
|
||||
break;
|
||||
}
|
||||
if (ch.exception.get() != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} 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 static class SpdyEchoTestServerHandler extends ChannelInboundHandlerAdapter {
|
||||
private final boolean autoRead;
|
||||
final AtomicReference<Throwable> exception = new AtomicReference<>();
|
||||
|
||||
SpdyEchoTestServerHandler(boolean autoRead) {
|
||||
this.autoRead = autoRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
ctx.write(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||
try {
|
||||
ctx.flush();
|
||||
} finally {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (exception.compareAndSet(null, cause)) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpdyEchoTestClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
private final boolean autoRead;
|
||||
final AtomicReference<Throwable> exception = new AtomicReference<>();
|
||||
final ByteBuf frames;
|
||||
volatile int counter;
|
||||
|
||||
SpdyEchoTestClientHandler(ByteBuf frames, boolean autoRead) {
|
||||
this.frames = frames;
|
||||
this.autoRead = autoRead;
|
||||
}
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
|
||||
byte[] actual = new byte[in.readableBytes()];
|
||||
in.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(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (exception.compareAndSet(null, cause)) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user