Overall refactoring of the STOMP codec

- StompObject -> StompSubframe
- StompFrame -> StompHeadersSubframe
- StompContent -> StompContntSubframe
- FullStompFrame -> StompFrame
- StompEncoder/Decoder -> StompSubframeEncoder/Decoder
- StompAggregator -> StompSubframeAggregator
- Simplify the example
- Update Javadoc
- Miscellaneous cleanup
This commit is contained in:
Trustin Lee 2014-06-04 16:39:50 +09:00
parent b286079205
commit a8143eda27
26 changed files with 533 additions and 594 deletions

View File

@ -20,7 +20,7 @@
<parent> <parent>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId> <artifactId>netty-parent</artifactId>
<version>4.1.0.Alpha1-SNAPSHOT</version> <version>5.0.0.Alpha2-SNAPSHOT</version>
</parent> </parent>
<artifactId>netty-codec-stomp</artifactId> <artifactId>netty-codec-stomp</artifactId>

View File

@ -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.handler.codec.stomp;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
/**
* Default implementation of {@link FullStompFrame}.
*/
public class DefaultFullStompFrame extends DefaultStompFrame implements FullStompFrame {
private final ByteBuf content;
public DefaultFullStompFrame(StompCommand command) {
this(command, Unpooled.buffer(0));
if (command == null) {
throw new NullPointerException("command");
}
}
public DefaultFullStompFrame(StompCommand command, ByteBuf content) {
super(command);
if (content == null) {
throw new NullPointerException("content");
}
this.content = content;
}
@Override
public ByteBuf content() {
return content;
}
@Override
public FullStompFrame copy() {
return new DefaultFullStompFrame(command, content.copy());
}
@Override
public FullStompFrame duplicate() {
return new DefaultFullStompFrame(command, content.duplicate());
}
@Override
public int refCnt() {
return content.refCnt();
}
@Override
public FullStompFrame retain() {
content.retain();
return this;
}
@Override
public FullStompFrame retain(int increment) {
content.retain();
return this;
}
@Override
public FullStompFrame touch() {
content.touch();
return this;
}
@Override
public FullStompFrame touch(Object hint) {
content.touch(hint);
return this;
}
@Override
public boolean release() {
return content.release();
}
@Override
public boolean release(int decrement) {
return content.release(decrement);
}
@Override
public String toString() {
return "DefaultFullStompFrame{" +
"command=" + command +
", headers=" + headers +
", content=" + content.toString(CharsetUtil.UTF_8) +
'}';
}
}

View File

@ -18,52 +18,52 @@ package io.netty.handler.codec.stomp;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
/** /**
* The default implementation for the {@link LastStompContent}. * The default implementation for the {@link LastStompContentSubframe}.
*/ */
public class DefaultLastStompContent extends DefaultStompContent implements LastStompContent { public class DefaultLastStompContentSubframe extends DefaultStompContentSubframe implements LastStompContentSubframe {
public DefaultLastStompContent(ByteBuf content) {
public DefaultLastStompContentSubframe(ByteBuf content) {
super(content); super(content);
} }
@Override @Override
public DefaultLastStompContent retain() { public DefaultLastStompContentSubframe retain() {
super.retain(); super.retain();
return this; return this;
} }
@Override @Override
public LastStompContent retain(int increment) { public LastStompContentSubframe retain(int increment) {
super.retain(increment); super.retain(increment);
return this; return this;
} }
@Override @Override
public LastStompContent touch() { public LastStompContentSubframe touch() {
super.touch(); super.touch();
return this; return this;
} }
@Override @Override
public LastStompContent touch(Object hint) { public LastStompContentSubframe touch(Object hint) {
super.touch(hint); super.touch(hint);
return this; return this;
} }
@Override @Override
public LastStompContent copy() { public LastStompContentSubframe copy() {
return new DefaultLastStompContent(content().copy()); return new DefaultLastStompContentSubframe(content().copy());
} }
@Override @Override
public LastStompContent duplicate() { public LastStompContentSubframe duplicate() {
return new DefaultLastStompContent(content().duplicate()); return new DefaultLastStompContentSubframe(content().duplicate());
} }
@Override @Override
public String toString() { public String toString() {
return "DefaultLastStompContent{" + return "DefaultLastStompContent{" +
"decoderResult=" + getDecoderResult() + "decoderResult=" + decoderResult() +
'}'; '}';
} }
} }

View File

@ -20,13 +20,13 @@ import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.DecoderResult;
/** /**
* The default {@link StompContent} implementation. * The default {@link StompContentSubframe} implementation.
*/ */
public class DefaultStompContent implements StompContent { public class DefaultStompContentSubframe implements StompContentSubframe {
private DecoderResult decoderResult; private DecoderResult decoderResult;
private final ByteBuf content; private final ByteBuf content;
public DefaultStompContent(ByteBuf content) { public DefaultStompContentSubframe(ByteBuf content) {
if (content == null) { if (content == null) {
throw new NullPointerException("content"); throw new NullPointerException("content");
} }
@ -39,13 +39,13 @@ public class DefaultStompContent implements StompContent {
} }
@Override @Override
public StompContent copy() { public StompContentSubframe copy() {
return new DefaultStompContent(content().copy()); return new DefaultStompContentSubframe(content().copy());
} }
@Override @Override
public StompContent duplicate() { public StompContentSubframe duplicate() {
return new DefaultStompContent(content().duplicate()); return new DefaultStompContentSubframe(content().duplicate());
} }
@Override @Override
@ -54,25 +54,25 @@ public class DefaultStompContent implements StompContent {
} }
@Override @Override
public StompContent retain() { public StompContentSubframe retain() {
content().retain(); content().retain();
return this; return this;
} }
@Override @Override
public StompContent retain(int increment) { public StompContentSubframe retain(int increment) {
content().retain(increment); content().retain(increment);
return this; return this;
} }
@Override @Override
public StompContent touch() { public StompContentSubframe touch() {
content.toString(); content.touch();
return this; return this;
} }
@Override @Override
public StompContent touch(Object hint) { public StompContentSubframe touch(Object hint) {
content.touch(hint); content.touch(hint);
return this; return this;
} }
@ -88,13 +88,13 @@ public class DefaultStompContent implements StompContent {
} }
@Override @Override
public DecoderResult getDecoderResult() { public DecoderResult decoderResult() {
return decoderResult; return decoderResult;
} }
@Override @Override
public void setDecoderResult(DecoderResult result) { public void setDecoderResult(DecoderResult decoderResult) {
this.decoderResult = result; this.decoderResult = decoderResult;
} }
@Override @Override

View File

@ -15,48 +15,92 @@
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import io.netty.handler.codec.DecoderResult; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
/** /**
* Default implementation of {@link StompFrame}. * Default implementation of {@link StompFrame}.
*/ */
public class DefaultStompFrame implements StompFrame { public class DefaultStompFrame extends DefaultStompHeadersSubframe implements StompFrame {
protected final StompCommand command;
protected DecoderResult decoderResult; private final ByteBuf content;
protected final StompHeaders headers = new StompHeaders();
public DefaultStompFrame(StompCommand command) { public DefaultStompFrame(StompCommand command) {
this(command, Unpooled.buffer(0));
if (command == null) { if (command == null) {
throw new NullPointerException("command"); throw new NullPointerException("command");
} }
this.command = command; }
public DefaultStompFrame(StompCommand command, ByteBuf content) {
super(command);
if (content == null) {
throw new NullPointerException("content");
}
this.content = content;
} }
@Override @Override
public StompCommand command() { public ByteBuf content() {
return command; return content;
} }
@Override @Override
public StompHeaders headers() { public StompFrame copy() {
return headers; return new DefaultStompFrame(command, content.copy());
} }
@Override @Override
public DecoderResult getDecoderResult() { public StompFrame duplicate() {
return decoderResult; return new DefaultStompFrame(command, content.duplicate());
} }
@Override @Override
public void setDecoderResult(DecoderResult decoderResult) { public int refCnt() {
this.decoderResult = decoderResult; return content.refCnt();
}
@Override
public StompFrame retain() {
content.retain();
return this;
}
@Override
public StompFrame retain(int increment) {
content.retain();
return this;
}
@Override
public StompFrame touch() {
content.touch();
return this;
}
@Override
public StompFrame touch(Object hint) {
content.touch(hint);
return this;
}
@Override
public boolean release() {
return content.release();
}
@Override
public boolean release(int decrement) {
return content.release(decrement);
} }
@Override @Override
public String toString() { public String toString() {
return "StompFrame{" + return "DefaultFullStompFrame{" +
"command=" + command + "command=" + command +
", headers=" + headers + ", headers=" + headers +
", content=" + content.toString(CharsetUtil.UTF_8) +
'}'; '}';
} }
} }

View File

@ -0,0 +1,63 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.stomp;
import io.netty.handler.codec.DecoderResult;
/**
* Default implementation of {@link StompHeadersSubframe}.
*/
public class DefaultStompHeadersSubframe implements StompHeadersSubframe {
protected final StompCommand command;
protected DecoderResult decoderResult;
protected final StompHeaders headers = new StompHeaders();
public DefaultStompHeadersSubframe(StompCommand command) {
if (command == null) {
throw new NullPointerException("command");
}
this.command = command;
}
@Override
public StompCommand command() {
return command;
}
@Override
public StompHeaders headers() {
return headers;
}
@Override
public DecoderResult decoderResult() {
return decoderResult;
}
@Override
public void setDecoderResult(DecoderResult decoderResult) {
this.decoderResult = decoderResult;
}
@Override
public String toString() {
return "StompFrame{" +
"command=" + command +
", headers=" + headers +
'}';
}
}

View File

@ -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.handler.codec.stomp;
/**
* Combines {@link StompFrame} and {@link LastStompContent} into one
* frame. So it represent a <i>complete</i> STOMP frame.
*/
public interface FullStompFrame extends StompFrame, LastStompContent {
@Override
FullStompFrame copy();
@Override
FullStompFrame duplicate();
@Override
FullStompFrame retain();
@Override
FullStompFrame retain(int increment);
@Override
FullStompFrame touch();
@Override
FullStompFrame touch(Object hint);
}

View File

@ -20,47 +20,47 @@ import io.netty.buffer.Unpooled;
import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.DecoderResult;
/** /**
* The last {@link StompContent} which signals the end of the content batch * The last {@link StompContentSubframe} which signals the end of the content batch
* <p/> * <p/>
* Note, even when no content is emitted by the protocol, an * Note, even when no content is emitted by the protocol, an
* empty {@link LastStompContent} is issued to make the upstream parsing * empty {@link LastStompContentSubframe} is issued to make the upstream parsing
* easier. * easier.
*/ */
public interface LastStompContent extends StompContent { public interface LastStompContentSubframe extends StompContentSubframe {
LastStompContent EMPTY_LAST_CONTENT = new LastStompContent() { LastStompContentSubframe EMPTY_LAST_CONTENT = new LastStompContentSubframe() {
@Override @Override
public ByteBuf content() { public ByteBuf content() {
return Unpooled.EMPTY_BUFFER; return Unpooled.EMPTY_BUFFER;
} }
@Override @Override
public LastStompContent copy() { public LastStompContentSubframe copy() {
return EMPTY_LAST_CONTENT; return EMPTY_LAST_CONTENT;
} }
@Override @Override
public LastStompContent duplicate() { public LastStompContentSubframe duplicate() {
return this; return this;
} }
@Override @Override
public LastStompContent retain() { public LastStompContentSubframe retain() {
return this; return this;
} }
@Override @Override
public LastStompContent retain(int increment) { public LastStompContentSubframe retain(int increment) {
return this; return this;
} }
@Override @Override
public LastStompContent touch() { public LastStompContentSubframe touch() {
return this; return this;
} }
@Override @Override
public LastStompContent touch(Object hint) { public LastStompContentSubframe touch(Object hint) {
return this; return this;
} }
@ -80,7 +80,7 @@ public interface LastStompContent extends StompContent {
} }
@Override @Override
public DecoderResult getDecoderResult() { public DecoderResult decoderResult() {
return DecoderResult.SUCCESS; return DecoderResult.SUCCESS;
} }
@ -91,21 +91,20 @@ public interface LastStompContent extends StompContent {
}; };
@Override @Override
LastStompContent copy(); LastStompContentSubframe copy();
@Override @Override
LastStompContent duplicate(); LastStompContentSubframe duplicate();
@Override @Override
LastStompContent retain(); LastStompContentSubframe retain();
@Override @Override
LastStompContent retain(int increment); LastStompContentSubframe retain(int increment);
@Override @Override
LastStompContent touch(); LastStompContentSubframe touch();
@Override @Override
LastStompContent touch(Object hint); LastStompContentSubframe touch(Object hint);
} }

View File

@ -19,7 +19,18 @@ package io.netty.handler.codec.stomp;
* STOMP command * STOMP command
*/ */
public enum StompCommand { public enum StompCommand {
STOMP, CONNECT, CONNECTED, SEND, SUBSCRIBE, UNSUBSCRIBE, ACK, NACK, BEGIN, DISCONNECT, MESSAGE, RECEIPT, ERROR, STOMP,
CONNECT,
CONNECTED,
SEND,
SUBSCRIBE,
UNSUBSCRIBE,
ACK,
NACK,
BEGIN,
DISCONNECT,
MESSAGE,
RECEIPT,
ERROR,
UNKNOWN UNKNOWN
} }

View File

@ -16,12 +16,11 @@
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
final class StompConstants { final class StompConstants {
public static final byte CR = 13;
public static final byte LF = 10;
public static final byte NULL = 0;
public static final byte COLON = 58;
private StompConstants() { static final byte CR = 13;
} static final byte LF = 10;
static final byte NUL = 0;
static final byte COLON = 58;
private StompConstants() { }
} }

View File

@ -16,32 +16,31 @@
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import io.netty.buffer.ByteBufHolder; import io.netty.buffer.ByteBufHolder;
import io.netty.channel.ChannelPipeline;
/** /**
* An STOMP chunk which is used for STOMP chunked transfer-encoding. * An STOMP chunk which is used for STOMP chunked transfer-encoding. {@link StompSubframeDecoder} generates
* {@link StompDecoder} generates {@link StompContent} after * {@link StompContentSubframe} after {@link StompHeadersSubframe} when the content is large or the encoding of
* {@link StompFrame} when the content is large or the encoding of the content * the content is 'chunked. If you prefer not to receive multiple {@link StompSubframe}s for a single
* is 'chunked. If you prefer not to receive {@link StompContent} in your handler, * {@link StompFrame}, place {@link StompSubframeAggregator} after {@link StompSubframeDecoder} in the
* place {@link StompAggregator} after {@link StompDecoder} in the * {@link ChannelPipeline}.
* {@link io.netty.channel.ChannelPipeline}.
*/ */
public interface StompContent extends ByteBufHolder, StompObject { public interface StompContentSubframe extends ByteBufHolder, StompSubframe {
@Override @Override
StompContent copy(); StompContentSubframe copy();
@Override @Override
StompContent duplicate(); StompContentSubframe duplicate();
@Override @Override
StompContent retain(); StompContentSubframe retain();
@Override @Override
StompContent retain(int increment); StompContentSubframe retain(int increment);
@Override @Override
StompContent touch(); StompContentSubframe touch();
@Override @Override
StompContent touch(Object hint); StompContentSubframe touch(Object hint);
} }

View File

@ -16,23 +16,25 @@
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
/** /**
* An interface that defines a Stomp frame * Combines {@link StompHeadersSubframe} and {@link LastStompContentSubframe} into one
* * frame. So it represent a <i>complete</i> STOMP frame.
* @see StompFrame
* @see FullStompFrame
* @see StompHeaders
*/ */
public interface StompFrame extends StompObject { public interface StompFrame extends StompHeadersSubframe, LastStompContentSubframe {
/** @Override
* returns command of this frame StompFrame copy();
* @return the command
*/
StompCommand command();
/** @Override
* returns headers of this frame StompFrame duplicate();
* @return the headers object
*/
StompHeaders headers();
@Override
StompFrame retain();
@Override
StompFrame retain(int increment);
@Override
StompFrame touch();
@Override
StompFrame touch(Object hint);
} }

View File

@ -18,16 +18,16 @@ package io.netty.handler.codec.stomp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
* Provides the constants for the standard STOMP header names and values and * Provides the constants for the standard STOMP header names and values and
* commonly used utility methods that accesses an {@link StompFrame}. * commonly used utility methods that accesses an {@link StompHeadersSubframe}.
*/ */
public class StompHeaders { public class StompHeaders {
public static final String ACCEPT_VERSION = "accept-version"; public static final String ACCEPT_VERSION = "accept-version";
public static final String HOST = "host"; public static final String HOST = "host";
public static final String LOGIN = "login"; public static final String LOGIN = "login";
@ -48,28 +48,16 @@ public class StompHeaders {
public static final String CONTENT_LENGTH = "content-length"; public static final String CONTENT_LENGTH = "content-length";
public static final String CONTENT_TYPE = "content-type"; public static final String CONTENT_TYPE = "content-type";
public static long getContentLength(StompHeaders headers, long defaultValue) {
String contentLength = headers.get(CONTENT_LENGTH);
if (contentLength != null) {
try {
return Long.parseLong(contentLength);
} catch (NumberFormatException e) {
return defaultValue;
}
}
return defaultValue;
}
private final Map<String, List<String>> headers = new HashMap<String, List<String>>(); private final Map<String, List<String>> headers = new HashMap<String, List<String>>();
public boolean has(String key) { public boolean has(String key) {
List<String> values = headers.get(key); List<String> values = headers.get(key);
return values != null && values.size() > 0; return values != null && !values.isEmpty();
} }
public String get(String key) { public String get(String key) {
List<String> values = headers.get(key); List<String> values = headers.get(key);
if (values != null && values.size() > 0) { if (values != null && !values.isEmpty()) {
return values.get(0); return values.get(0);
} else { } else {
return null; return null;
@ -85,6 +73,7 @@ public class StompHeaders {
values.add(value); values.add(value);
} }
@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
public void set(String key, String value) { public void set(String key, String value) {
headers.put(key, Arrays.asList(value)); headers.put(key, Arrays.asList(value));
} }
@ -110,8 +99,7 @@ public class StompHeaders {
} }
public void set(StompHeaders headers) { public void set(StompHeaders headers) {
for (Iterator<String> iterator = headers.keySet().iterator(); iterator.hasNext();) { for (String key: headers.keySet()) {
String key = iterator.next();
List<String> values = headers.getAll(key); List<String> values = headers.getAll(key);
this.headers.put(key, values); this.headers.put(key, values);
} }

View File

@ -13,13 +13,22 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package io.netty.example.stomp; package io.netty.handler.codec.stomp;
import io.netty.handler.codec.stomp.FullStompFrame;
/** /**
* STOMP frame listener which used as a callback in {@link StompClientHandler} * An interface that defines a {@link StompFrame}'s command and headers.
*
* @see StompCommand
* @see StompHeaders
*/ */
public interface StompFrameListener { public interface StompHeadersSubframe extends StompSubframe {
void onFrame(FullStompFrame frame); /**
* Returns command of this frame.
*/
StompCommand command();
/**
* Returns headers of this frame.
*/
StompHeaders headers();
} }

View File

@ -18,18 +18,17 @@ package io.netty.handler.codec.stomp;
import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.DecoderResult;
/** /**
* Defines a common interface for all {@link StompObject} implementations. * Defines a common interface for all {@link StompSubframe} implementations.
*/ */
public interface StompObject { public interface StompSubframe {
/** /**
* Returns the result of decoding this object. * Returns the result of decoding this object.
*/ */
DecoderResult getDecoderResult(); DecoderResult decoderResult();
/** /**
* Updates the result of decoding this object. This method is supposed to be invoked by {@link StompDecoder}. * Updates the result of decoding this object. This method is supposed to be invoked by
* Do not call this method unless you know what you are doing. * {@link StompSubframeDecoder}. Do not call this method unless you know what you are doing.
*/ */
void setDecoderResult(DecoderResult result); void setDecoderResult(DecoderResult result);
} }

View File

@ -15,28 +15,30 @@
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import java.util.List;
import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.TooLongFrameException;
import java.util.List;
/** /**
* A {@link io.netty.channel.ChannelHandler} that aggregates an {@link StompFrame} * A {@link ChannelHandler} that aggregates an {@link StompHeadersSubframe}
* and its following {@link StompContent}s into a single {@link StompFrame} with * and its following {@link StompContentSubframe}s into a single {@link StompFrame}.
* no following {@link StompContent}s. It is useful when you don't want to take * It is useful when you don't want to take care of STOMP frames whose content is 'chunked'. Insert this
* care of STOMP frames whose content is 'chunked'. Insert this * handler after {@link StompSubframeDecoder} in the {@link ChannelPipeline}:
* handler after {@link StompDecoder} in the {@link io.netty.channel.ChannelPipeline}:
*/ */
public class StompAggregator extends MessageToMessageDecoder<StompObject> { public class StompSubframeAggregator extends MessageToMessageDecoder<StompSubframe> {
public static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024;
private static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024;
private int maxCumulationBufferComponents = DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS; private int maxCumulationBufferComponents = DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS;
private final int maxContentLength; private final int maxContentLength;
private FullStompFrame currentFrame; private StompFrame currentFrame;
private boolean tooLongFrameFound; private boolean tooLongFrameFound;
private volatile boolean handlerAdded; private volatile boolean handlerAdded;
@ -48,7 +50,7 @@ public class StompAggregator extends MessageToMessageDecoder<StompObject> {
* If the length of the aggregated content exceeds this value, * If the length of the aggregated content exceeds this value,
* a {@link TooLongFrameException} will be raised. * a {@link TooLongFrameException} will be raised.
*/ */
public StompAggregator(int maxContentLength) { public StompSubframeAggregator(int maxContentLength) {
if (maxContentLength <= 0) { if (maxContentLength <= 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"maxContentLength must be a positive integer: " + "maxContentLength must be a positive integer: " +
@ -80,23 +82,23 @@ public class StompAggregator extends MessageToMessageDecoder<StompObject> {
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, StompObject msg, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, StompSubframe msg, List<Object> out) throws Exception {
FullStompFrame currentFrame = this.currentFrame; StompFrame currentFrame = this.currentFrame;
if (msg instanceof StompFrame) { if (msg instanceof StompHeadersSubframe) {
assert currentFrame == null; assert currentFrame == null;
StompFrame frame = (StompFrame) msg; StompHeadersSubframe frame = (StompHeadersSubframe) msg;
this.currentFrame = currentFrame = new DefaultFullStompFrame(frame.command(), this.currentFrame = currentFrame = new DefaultStompFrame(frame.command(),
Unpooled.compositeBuffer(maxCumulationBufferComponents)); Unpooled.compositeBuffer(maxCumulationBufferComponents));
currentFrame.headers().set(frame.headers()); currentFrame.headers().set(frame.headers());
} else if (msg instanceof StompContent) { } else if (msg instanceof StompContentSubframe) {
if (tooLongFrameFound) { if (tooLongFrameFound) {
if (msg instanceof LastStompContent) { if (msg instanceof LastStompContentSubframe) {
this.currentFrame = null; this.currentFrame = null;
} }
return; return;
} }
assert currentFrame != null; assert currentFrame != null;
StompContent chunk = (StompContent) msg; StompContentSubframe chunk = (StompContentSubframe) msg;
CompositeByteBuf contentBuf = (CompositeByteBuf) currentFrame.content(); CompositeByteBuf contentBuf = (CompositeByteBuf) currentFrame.content();
if (contentBuf.readableBytes() > maxContentLength - chunk.content().readableBytes()) { if (contentBuf.readableBytes() > maxContentLength - chunk.content().readableBytes()) {
tooLongFrameFound = true; tooLongFrameFound = true;
@ -109,7 +111,7 @@ public class StompAggregator extends MessageToMessageDecoder<StompObject> {
contentBuf.addComponent(chunk.retain().content()); contentBuf.addComponent(chunk.retain().content());
contentBuf.writerIndex(contentBuf.writerIndex() + chunk.content().readableBytes()); contentBuf.writerIndex(contentBuf.writerIndex() + chunk.content().readableBytes());
if (chunk instanceof LastStompContent) { if (chunk instanceof LastStompContentSubframe) {
out.add(currentFrame); out.add(currentFrame);
this.currentFrame = null; this.currentFrame = null;
} }
@ -121,7 +123,7 @@ public class StompAggregator extends MessageToMessageDecoder<StompObject> {
@Override @Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx); super.handlerAdded(ctx);
this.handlerAdded = true; handlerAdded = true;
} }
@Override @Override
@ -136,7 +138,7 @@ public class StompAggregator extends MessageToMessageDecoder<StompObject> {
@Override @Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
super.handlerRemoved(ctx); super.handlerRemoved(ctx);
this.handlerAdded = false; handlerAdded = false;
if (currentFrame != null) { if (currentFrame != null) {
currentFrame.release(); currentFrame.release();
currentFrame = null; currentFrame = null;

View File

@ -15,23 +15,25 @@
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import java.util.List;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.ReplayingDecoder; import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.stomp.StompDecoder.State; import io.netty.handler.codec.stomp.StompSubframeDecoder.State;
import io.netty.util.internal.AppendableCharSequence;
import io.netty.util.internal.StringUtil;
import static io.netty.buffer.ByteBufUtil.readBytes; import java.util.List;
import java.util.Locale;
import static io.netty.buffer.ByteBufUtil.*;
/** /**
* Decodes {@link ByteBuf}s into {@link StompFrame}s and * Decodes {@link ByteBuf}s into {@link StompHeadersSubframe}s and
* {@link StompContent}s. * {@link StompContentSubframe}s.
* *
* <h3>Parameters to control memory consumption: </h3> * <h3>Parameters to control memory consumption: </h3>
* {@code maxLineLength} the maximum length of line - * {@code maxLineLength} the maximum length of line -
@ -42,31 +44,42 @@ import static io.netty.buffer.ByteBufUtil.readBytes;
* {@code maxChunkSize} * {@code maxChunkSize}
* The maximum length of the content or each chunk. If the content length * The maximum length of the content or each chunk. If the content length
* (or the length of each chunk) exceeds this value, the content or chunk * (or the length of each chunk) exceeds this value, the content or chunk
* ill be split into multiple {@link StompContent}s whose length is * ill be split into multiple {@link StompContentSubframe}s whose length is
* {@code maxChunkSize} at maximum. * {@code maxChunkSize} at maximum.
* *
* <h3>Chunked Content</h3> * <h3>Chunked Content</h3>
* *
* If the content of a stomp message is greater than {@code maxChunkSize} * If the content of a stomp message is greater than {@code maxChunkSize}
* the transfer encoding of the HTTP message is 'chunked', this decoder * the transfer encoding of the HTTP message is 'chunked', this decoder
* generates multiple {@link StompContent} instances to avoid excessive memory * generates multiple {@link StompContentSubframe} instances to avoid excessive memory
* consumption. Note, that every message, even with no content decodes with * consumption. Note, that every message, even with no content decodes with
* {@link LastStompContent} at the end to simplify upstream message parsing. * {@link LastStompContentSubframe} at the end to simplify upstream message parsing.
*/ */
public class StompDecoder extends ReplayingDecoder<State> { public class StompSubframeDecoder extends ReplayingDecoder<State> {
public static final int DEFAULT_CHUNK_SIZE = 8132;
public static final int DEFAULT_MAX_LINE_LENGTH = 1024; private static final int DEFAULT_CHUNK_SIZE = 8132;
private int maxLineLength; private static final int DEFAULT_MAX_LINE_LENGTH = 1024;
private int maxChunkSize;
enum State {
SKIP_CONTROL_CHARACTERS,
READ_HEADERS,
READ_CONTENT,
FINALIZE_FRAME_READ,
BAD_FRAME,
INVALID_CHUNK
}
private final int maxLineLength;
private final int maxChunkSize;
private int alreadyReadChunkSize; private int alreadyReadChunkSize;
private LastStompContent lastContent; private LastStompContentSubframe lastContent;
private long contentLength; private long contentLength;
public StompDecoder() { public StompSubframeDecoder() {
this(DEFAULT_MAX_LINE_LENGTH, DEFAULT_CHUNK_SIZE); this(DEFAULT_MAX_LINE_LENGTH, DEFAULT_CHUNK_SIZE);
} }
public StompDecoder(int maxLineLength, int maxChunkSize) { public StompSubframeDecoder(int maxLineLength, int maxChunkSize) {
super(State.SKIP_CONTROL_CHARACTERS); super(State.SKIP_CONTROL_CHARACTERS);
if (maxLineLength <= 0) { if (maxLineLength <= 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -88,17 +101,18 @@ public class StompDecoder extends ReplayingDecoder<State> {
case SKIP_CONTROL_CHARACTERS: case SKIP_CONTROL_CHARACTERS:
skipControlCharacters(in); skipControlCharacters(in);
checkpoint(State.READ_HEADERS); checkpoint(State.READ_HEADERS);
// Fall through.
case READ_HEADERS: case READ_HEADERS:
StompCommand command = StompCommand.UNKNOWN; StompCommand command = StompCommand.UNKNOWN;
StompFrame frame = null; StompHeadersSubframe frame = null;
try { try {
command = readCommand(in); command = readCommand(in);
frame = new DefaultStompFrame(command); frame = new DefaultStompHeadersSubframe(command);
checkpoint(readHeaders(in, frame.headers())); checkpoint(readHeaders(in, frame.headers()));
out.add(frame); out.add(frame);
} catch (Exception e) { } catch (Exception e) {
if (frame == null) { if (frame == null) {
frame = new DefaultStompFrame(command); frame = new DefaultStompHeadersSubframe(command);
} }
frame.setDecoderResult(DecoderResult.failure(e)); frame.setDecoderResult(DecoderResult.failure(e));
out.add(frame); out.add(frame);
@ -126,27 +140,27 @@ public class StompDecoder extends ReplayingDecoder<State> {
} }
ByteBuf chunkBuffer = readBytes(ctx.alloc(), in, toRead); ByteBuf chunkBuffer = readBytes(ctx.alloc(), in, toRead);
if ((alreadyReadChunkSize += toRead) >= contentLength) { if ((alreadyReadChunkSize += toRead) >= contentLength) {
lastContent = new DefaultLastStompContent(chunkBuffer); lastContent = new DefaultLastStompContentSubframe(chunkBuffer);
checkpoint(State.FINALIZE_FRAME_READ); checkpoint(State.FINALIZE_FRAME_READ);
} else { } else {
DefaultStompContent chunk; DefaultStompContentSubframe chunk;
chunk = new DefaultStompContent(chunkBuffer); chunk = new DefaultStompContentSubframe(chunkBuffer);
out.add(chunk); out.add(chunk);
} }
if (alreadyReadChunkSize < contentLength) { if (alreadyReadChunkSize < contentLength) {
return; return;
} }
//fall through // Fall through.
case FINALIZE_FRAME_READ: case FINALIZE_FRAME_READ:
skipNullCharacter(in); skipNullCharacter(in);
if (lastContent == null) { if (lastContent == null) {
lastContent = LastStompContent.EMPTY_LAST_CONTENT; lastContent = LastStompContentSubframe.EMPTY_LAST_CONTENT;
} }
out.add(lastContent); out.add(lastContent);
resetDecoder(); resetDecoder();
} }
} catch (Exception e) { } catch (Exception e) {
StompContent errorContent = new DefaultLastStompContent(Unpooled.EMPTY_BUFFER); StompContentSubframe errorContent = new DefaultLastStompContentSubframe(Unpooled.EMPTY_BUFFER);
errorContent.setDecoderResult(DecoderResult.failure(e)); errorContent.setDecoderResult(DecoderResult.failure(e));
out.add(errorContent); out.add(errorContent);
checkpoint(State.BAD_FRAME); checkpoint(State.BAD_FRAME);
@ -162,7 +176,7 @@ public class StompDecoder extends ReplayingDecoder<State> {
//do nothing //do nothing
} }
if (command == null) { if (command == null) {
commandStr = commandStr.toUpperCase(); commandStr = commandStr.toUpperCase(Locale.US);
try { try {
command = StompCommand.valueOf(commandStr); command = StompCommand.valueOf(commandStr);
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
@ -176,20 +190,20 @@ public class StompDecoder extends ReplayingDecoder<State> {
} }
private State readHeaders(ByteBuf buffer, StompHeaders headers) { private State readHeaders(ByteBuf buffer, StompHeaders headers) {
while (true) { for (;;) {
String line = readLine(buffer, maxLineLength); String line = readLine(buffer, maxLineLength);
if (line.length() > 0) { if (!line.isEmpty()) {
String[] split = line.split(":"); String[] split = StringUtil.split(line, ':');
if (split.length == 2) { if (split.length == 2) {
headers.add(split[0], split[1]); headers.add(split[0], split[1]);
} }
} else { } else {
long contentLength = -1; long contentLength = -1;
if (headers.has(StompHeaders.CONTENT_LENGTH)) { if (headers.has(StompHeaders.CONTENT_LENGTH)) {
contentLength = StompHeaders.getContentLength(headers, 0); contentLength = getContentLength(headers, 0);
} else { } else {
int globalIndex = ByteBufUtil.indexOf(buffer, buffer.readerIndex(), int globalIndex = indexOf(buffer, buffer.readerIndex(),
buffer.writerIndex(), StompConstants.NULL); buffer.writerIndex(), StompConstants.NUL);
if (globalIndex != -1) { if (globalIndex != -1) {
contentLength = globalIndex - buffer.readerIndex(); contentLength = globalIndex - buffer.readerIndex();
} }
@ -204,16 +218,28 @@ public class StompDecoder extends ReplayingDecoder<State> {
} }
} }
private static long getContentLength(StompHeaders headers, long defaultValue) {
String contentLength = headers.get(StompHeaders.CONTENT_LENGTH);
if (contentLength != null) {
try {
return Long.parseLong(contentLength);
} catch (NumberFormatException ignored) {
return defaultValue;
}
}
return defaultValue;
}
private static void skipNullCharacter(ByteBuf buffer) { private static void skipNullCharacter(ByteBuf buffer) {
byte b = buffer.readByte(); byte b = buffer.readByte();
if (b != StompConstants.NULL) { if (b != StompConstants.NUL) {
throw new IllegalStateException("unexpected byte in buffer " + b + " while expecting NULL byte"); throw new IllegalStateException("unexpected byte in buffer " + b + " while expecting NULL byte");
} }
} }
private static void skipControlCharacters(ByteBuf buffer) { private static void skipControlCharacters(ByteBuf buffer) {
byte b; byte b;
while (true) { for (;;) {
b = buffer.readByte(); b = buffer.readByte();
if (b != StompConstants.CR && b != StompConstants.LF) { if (b != StompConstants.CR && b != StompConstants.LF) {
buffer.readerIndex(buffer.readerIndex() - 1); buffer.readerIndex(buffer.readerIndex() - 1);
@ -223,23 +249,23 @@ public class StompDecoder extends ReplayingDecoder<State> {
} }
private static String readLine(ByteBuf buffer, int maxLineLength) { private static String readLine(ByteBuf buffer, int maxLineLength) {
StringBuilder sb = new StringBuilder(); AppendableCharSequence buf = new AppendableCharSequence(128);
int lineLength = 0; int lineLength = 0;
while (true) { for (;;) {
byte nextByte = buffer.readByte(); byte nextByte = buffer.readByte();
if (nextByte == StompConstants.CR) { if (nextByte == StompConstants.CR) {
nextByte = buffer.readByte(); nextByte = buffer.readByte();
if (nextByte == StompConstants.LF) { if (nextByte == StompConstants.LF) {
return sb.toString(); return buf.toString();
} }
} else if (nextByte == StompConstants.LF) { } else if (nextByte == StompConstants.LF) {
return sb.toString(); return buf.toString();
} else { } else {
if (lineLength >= maxLineLength) { if (lineLength >= maxLineLength) {
throw new TooLongFrameException("An STOMP line is larger than " + maxLineLength + " bytes."); throw new TooLongFrameException("An STOMP line is larger than " + maxLineLength + " bytes.");
} }
lineLength ++; lineLength ++;
sb.append((char) nextByte); buf.append((char) nextByte);
} }
} }
} }
@ -250,14 +276,4 @@ public class StompDecoder extends ReplayingDecoder<State> {
alreadyReadChunkSize = 0; alreadyReadChunkSize = 0;
lastContent = null; lastContent = null;
} }
enum State {
SKIP_CONTROL_CHARACTERS,
READ_HEADERS,
READ_CONTENT,
FINALIZE_FRAME_READ,
BAD_FRAME,
INVALID_CHUNK
}
} }

View File

@ -15,70 +15,64 @@
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import java.util.Iterator;
import java.util.List;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import java.util.List;
/** /**
* Encodes a {@link StompFrame} or a {@link FullStompFrame} or a {@link StompContent} into a {@link ByteBuf}. * Encodes a {@link StompFrame} or a {@link StompSubframe} into a {@link ByteBuf}.
*/ */
public class StompEncoder extends MessageToMessageEncoder<StompObject> { public class StompSubframeEncoder extends MessageToMessageEncoder<StompSubframe> {
@Override @Override
protected void encode(ChannelHandlerContext ctx, StompObject msg, List<Object> out) throws Exception { protected void encode(ChannelHandlerContext ctx, StompSubframe msg, List<Object> out) throws Exception {
if (msg instanceof FullStompFrame) { if (msg instanceof StompFrame) {
FullStompFrame frame = (FullStompFrame) msg; StompFrame frame = (StompFrame) msg;
ByteBuf frameBuf = encodeFrame(frame, ctx); ByteBuf frameBuf = encodeFrame(frame, ctx);
out.add(frameBuf); out.add(frameBuf);
ByteBuf contentBuf = encodeContent(frame, ctx); ByteBuf contentBuf = encodeContent(frame, ctx);
out.add(contentBuf); out.add(contentBuf);
} else if (msg instanceof StompFrame) { } else if (msg instanceof StompHeadersSubframe) {
StompFrame frame = (StompFrame) msg; StompHeadersSubframe frame = (StompHeadersSubframe) msg;
ByteBuf buf = encodeFrame(frame, ctx); ByteBuf buf = encodeFrame(frame, ctx);
out.add(buf); out.add(buf);
} else if (msg instanceof StompContent) { } else if (msg instanceof StompContentSubframe) {
StompContent stompContent = (StompContent) msg; StompContentSubframe stompContentSubframe = (StompContentSubframe) msg;
ByteBuf buf = encodeContent(stompContent, ctx); ByteBuf buf = encodeContent(stompContentSubframe, ctx);
out.add(buf); out.add(buf);
} }
} }
private ByteBuf encodeContent(StompContent content, ChannelHandlerContext ctx) { private static ByteBuf encodeContent(StompContentSubframe content, ChannelHandlerContext ctx) {
if (content instanceof LastStompContent) { if (content instanceof LastStompContentSubframe) {
ByteBuf buf = ctx.alloc().buffer(content.content().readableBytes() + 1); ByteBuf buf = ctx.alloc().buffer(content.content().readableBytes() + 1);
buf.writeBytes(content.content()); buf.writeBytes(content.content());
buf.writeByte(StompConstants.NULL); buf.writeByte(StompConstants.NUL);
return buf; return buf;
} else { } else {
ByteBuf buf = ctx.alloc().buffer(content.content().readableBytes()); return content.content().retain();
buf.writeBytes(content.content());
return buf;
} }
} }
private ByteBuf encodeFrame(StompFrame frame, ChannelHandlerContext ctx) { private static ByteBuf encodeFrame(StompHeadersSubframe frame, ChannelHandlerContext ctx) {
ByteBuf buf = ctx.alloc().buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII)); buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF); buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
StompHeaders headers = frame.headers(); StompHeaders headers = frame.headers();
for (Iterator<String> iterator = headers.keySet().iterator(); iterator.hasNext();) { for (String k: headers.keySet()) {
String key = iterator.next(); List<String> values = headers.getAll(k);
List<String> values = headers.getAll(key); for (String v: values) {
for (Iterator<String> stringIterator = values.iterator(); stringIterator.hasNext();) { buf.writeBytes(k.getBytes(CharsetUtil.US_ASCII)).
String value = stringIterator.next(); writeByte(StompConstants.COLON).writeBytes(v.getBytes(CharsetUtil.US_ASCII));
buf.writeBytes(key.getBytes(CharsetUtil.US_ASCII)).
writeByte(StompConstants.COLON).writeBytes(value.getBytes(CharsetUtil.US_ASCII));
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF); buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
} }
} }
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF); buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
return buf; return buf;
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2013 The Netty Project * Copyright 2014 The Netty Project
* *
* The Netty Project licenses this file to you under the Apache License, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -15,6 +15,6 @@
*/ */
/** /**
* Common superset of ascii and binary classes. * <a href="http://en.wikipedia.org/wiki/Streaming_Text_Oriented_Messaging_Protocol">STOMP</a> codec
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;

View File

@ -15,24 +15,23 @@
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import io.netty.util.CharsetUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.CharsetUtil;
import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class StompAggregatorTest { public class StompSubframeAggregatorTest {
private EmbeddedChannel channel; private EmbeddedChannel channel;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
channel = new EmbeddedChannel(new StompDecoder(), new StompAggregator(100000)); channel = new EmbeddedChannel(new StompSubframeDecoder(), new StompSubframeAggregator(100000));
} }
@After @After
@ -45,8 +44,8 @@ public class StompAggregatorTest {
ByteBuf incoming = Unpooled.buffer(); ByteBuf incoming = Unpooled.buffer();
incoming.writeBytes(StompTestConstants.CONNECT_FRAME.getBytes()); incoming.writeBytes(StompTestConstants.CONNECT_FRAME.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
StompFrame frame = channel.readInbound(); StompHeadersSubframe frame = channel.readInbound();
Assert.assertTrue(frame instanceof FullStompFrame); Assert.assertTrue(frame instanceof StompFrame);
Assert.assertNull(channel.readInbound()); Assert.assertNull(channel.readInbound());
} }
@ -55,7 +54,7 @@ public class StompAggregatorTest {
ByteBuf incoming = Unpooled.buffer(); ByteBuf incoming = Unpooled.buffer();
incoming.writeBytes(StompTestConstants.SEND_FRAME_2.getBytes()); incoming.writeBytes(StompTestConstants.SEND_FRAME_2.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
FullStompFrame frame = channel.readInbound(); StompFrame frame = channel.readInbound();
Assert.assertNotNull(frame); Assert.assertNotNull(frame);
Assert.assertEquals(StompCommand.SEND, frame.command()); Assert.assertEquals(StompCommand.SEND, frame.command());
Assert.assertEquals("hello, queue a!!!", frame.content().toString(CharsetUtil.UTF_8)); Assert.assertEquals("hello, queue a!!!", frame.content().toString(CharsetUtil.UTF_8));
@ -64,11 +63,12 @@ public class StompAggregatorTest {
@Test @Test
public void testSingleFrameChunked() { public void testSingleFrameChunked() {
EmbeddedChannel channel = new EmbeddedChannel(new StompDecoder(10000, 5), new StompAggregator(100000)); EmbeddedChannel channel = new EmbeddedChannel(
new StompSubframeDecoder(10000, 5), new StompSubframeAggregator(100000));
ByteBuf incoming = Unpooled.buffer(); ByteBuf incoming = Unpooled.buffer();
incoming.writeBytes(StompTestConstants.SEND_FRAME_2.getBytes()); incoming.writeBytes(StompTestConstants.SEND_FRAME_2.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
FullStompFrame frame = channel.readInbound(); StompFrame frame = channel.readInbound();
Assert.assertNotNull(frame); Assert.assertNotNull(frame);
Assert.assertEquals(StompCommand.SEND, frame.command()); Assert.assertEquals(StompCommand.SEND, frame.command());
Assert.assertNull(channel.readInbound()); Assert.assertNull(channel.readInbound());
@ -81,7 +81,7 @@ public class StompAggregatorTest {
incoming.writeBytes(StompTestConstants.CONNECTED_FRAME.getBytes()); incoming.writeBytes(StompTestConstants.CONNECTED_FRAME.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
channel.writeInbound(Unpooled.wrappedBuffer(StompTestConstants.SEND_FRAME_1.getBytes())); channel.writeInbound(Unpooled.wrappedBuffer(StompTestConstants.SEND_FRAME_1.getBytes()));
FullStompFrame frame = channel.readInbound(); StompFrame frame = channel.readInbound();
Assert.assertEquals(StompCommand.CONNECT, frame.command()); Assert.assertEquals(StompCommand.CONNECT, frame.command());
frame = channel.readInbound(); frame = channel.readInbound();
Assert.assertEquals(StompCommand.CONNECTED, frame.command()); Assert.assertEquals(StompCommand.CONNECTED, frame.command());
@ -92,8 +92,7 @@ public class StompAggregatorTest {
@Test(expected = TooLongFrameException.class) @Test(expected = TooLongFrameException.class)
public void testTooLongFrameException() { public void testTooLongFrameException() {
EmbeddedChannel channel = new EmbeddedChannel(new StompDecoder(), new StompAggregator(10)); EmbeddedChannel channel = new EmbeddedChannel(new StompSubframeDecoder(), new StompSubframeAggregator(10));
channel.writeInbound(Unpooled.wrappedBuffer(StompTestConstants.SEND_FRAME_1.getBytes())); channel.writeInbound(Unpooled.wrappedBuffer(StompTestConstants.SEND_FRAME_1.getBytes()));
} }
} }

View File

@ -15,28 +15,28 @@
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import io.netty.buffer.ByteBuf; import static org.junit.Assert.*;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.Assert;
public class StompDecoderTest { public class StompSubframeDecoderTest {
private EmbeddedChannel channel; private EmbeddedChannel channel;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
channel = new EmbeddedChannel(new StompDecoder()); channel = new EmbeddedChannel(new StompSubframeDecoder());
} }
@After @After
public void teardown() throws Exception { public void teardown() throws Exception {
Assert.assertFalse(channel.finish()); assertFalse(channel.finish());
} }
@Test @Test
@ -44,13 +44,13 @@ public class StompDecoderTest {
ByteBuf incoming = Unpooled.buffer(); ByteBuf incoming = Unpooled.buffer();
incoming.writeBytes(StompTestConstants.CONNECT_FRAME.getBytes()); incoming.writeBytes(StompTestConstants.CONNECT_FRAME.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
StompFrame frame = channel.readInbound(); StompHeadersSubframe frame = channel.readInbound();
Assert.assertNotNull(frame); assertNotNull(frame);
Assert.assertEquals(StompCommand.CONNECT, frame.command()); assertEquals(StompCommand.CONNECT, frame.command());
StompContent content = channel.readInbound(); StompContentSubframe content = channel.readInbound();
Assert.assertTrue(content == LastStompContent.EMPTY_LAST_CONTENT); assertSame(content, LastStompContentSubframe.EMPTY_LAST_CONTENT);
Object o = channel.readInbound(); Object o = channel.readInbound();
Assert.assertNull(o); assertNull(o);
} }
@Test @Test
@ -58,14 +58,14 @@ public class StompDecoderTest {
ByteBuf incoming = Unpooled.buffer(); ByteBuf incoming = Unpooled.buffer();
incoming.writeBytes(StompTestConstants.SEND_FRAME_2.getBytes()); incoming.writeBytes(StompTestConstants.SEND_FRAME_2.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
StompFrame frame = channel.readInbound(); StompHeadersSubframe frame = channel.readInbound();
Assert.assertNotNull(frame); assertNotNull(frame);
Assert.assertEquals(StompCommand.SEND, frame.command()); assertEquals(StompCommand.SEND, frame.command());
StompContent content = channel.readInbound(); StompContentSubframe content = channel.readInbound();
Assert.assertTrue(content instanceof LastStompContent); assertTrue(content instanceof LastStompContentSubframe);
String s = content.content().toString(CharsetUtil.UTF_8); String s = content.content().toString(CharsetUtil.UTF_8);
Assert.assertEquals("hello, queue a!!!", s); assertEquals("hello, queue a!!!", s);
Assert.assertNull(channel.readInbound()); assertNull(channel.readInbound());
} }
@Test @Test
@ -73,43 +73,43 @@ public class StompDecoderTest {
ByteBuf incoming = Unpooled.buffer(); ByteBuf incoming = Unpooled.buffer();
incoming.writeBytes(StompTestConstants.SEND_FRAME_1.getBytes()); incoming.writeBytes(StompTestConstants.SEND_FRAME_1.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
StompFrame frame = (StompFrame) channel.readInbound(); StompHeadersSubframe frame = channel.readInbound();
Assert.assertNotNull(frame); assertNotNull(frame);
Assert.assertEquals(StompCommand.SEND, frame.command()); assertEquals(StompCommand.SEND, frame.command());
StompContent content = (StompContent) channel.readInbound(); StompContentSubframe content = channel.readInbound();
Assert.assertTrue(content instanceof LastStompContent); assertTrue(content instanceof LastStompContentSubframe);
String s = content.content().toString(CharsetUtil.UTF_8); String s = content.content().toString(CharsetUtil.UTF_8);
Assert.assertEquals("hello, queue a!", s); assertEquals("hello, queue a!", s);
Assert.assertNull(channel.readInbound()); assertNull(channel.readInbound());
} }
@Test @Test
public void testSingleFrameChunked() { public void testSingleFrameChunked() {
EmbeddedChannel channel = new EmbeddedChannel(new StompDecoder(10000, 5)); EmbeddedChannel channel = new EmbeddedChannel(new StompSubframeDecoder(10000, 5));
ByteBuf incoming = Unpooled.buffer(); ByteBuf incoming = Unpooled.buffer();
incoming.writeBytes(StompTestConstants.SEND_FRAME_2.getBytes()); incoming.writeBytes(StompTestConstants.SEND_FRAME_2.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
StompFrame frame = channel.readInbound(); StompHeadersSubframe frame = channel.readInbound();
Assert.assertNotNull(frame); assertNotNull(frame);
Assert.assertEquals(StompCommand.SEND, frame.command()); assertEquals(StompCommand.SEND, frame.command());
StompContent content = channel.readInbound(); StompContentSubframe content = channel.readInbound();
String s = content.content().toString(CharsetUtil.UTF_8); String s = content.content().toString(CharsetUtil.UTF_8);
Assert.assertEquals("hello", s); assertEquals("hello", s);
content = channel.readInbound(); content = channel.readInbound();
s = content.content().toString(CharsetUtil.UTF_8); s = content.content().toString(CharsetUtil.UTF_8);
Assert.assertEquals(", que", s); assertEquals(", que", s);
content = channel.readInbound(); content = channel.readInbound();
s = content.content().toString(CharsetUtil.UTF_8); s = content.content().toString(CharsetUtil.UTF_8);
Assert.assertEquals("ue a!", s); assertEquals("ue a!", s);
content = channel.readInbound(); content = channel.readInbound();
s = content.content().toString(CharsetUtil.UTF_8); s = content.content().toString(CharsetUtil.UTF_8);
Assert.assertEquals("!!", s); assertEquals("!!", s);
Assert.assertNull(channel.readInbound()); assertNull(channel.readInbound());
} }
@Test @Test
@ -119,17 +119,17 @@ public class StompDecoderTest {
incoming.writeBytes(StompTestConstants.CONNECTED_FRAME.getBytes()); incoming.writeBytes(StompTestConstants.CONNECTED_FRAME.getBytes());
channel.writeInbound(incoming); channel.writeInbound(incoming);
StompFrame frame = channel.readInbound(); StompHeadersSubframe frame = channel.readInbound();
Assert.assertNotNull(frame); assertNotNull(frame);
Assert.assertEquals(StompCommand.CONNECT, frame.command()); assertEquals(StompCommand.CONNECT, frame.command());
StompContent content = channel.readInbound(); StompContentSubframe content = channel.readInbound();
Assert.assertTrue(content == LastStompContent.EMPTY_LAST_CONTENT); assertSame(content, LastStompContentSubframe.EMPTY_LAST_CONTENT);
StompFrame frame2 = channel.readInbound(); StompHeadersSubframe frame2 = channel.readInbound();
Assert.assertNotNull(frame2); assertNotNull(frame2);
Assert.assertEquals(StompCommand.CONNECTED, frame2.command()); assertEquals(StompCommand.CONNECTED, frame2.command());
StompContent content2 = channel.readInbound(); StompContentSubframe content2 = channel.readInbound();
Assert.assertTrue(content2 == LastStompContent.EMPTY_LAST_CONTENT); assertSame(content2, LastStompContentSubframe.EMPTY_LAST_CONTENT);
Assert.assertNull(channel.readInbound()); assertNull(channel.readInbound());
} }
} }

View File

@ -15,49 +15,48 @@
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import io.netty.buffer.ByteBuf; import static org.junit.Assert.*;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.Assert;
public class StompEncoderTest { public class StompSubframeEncoderTest {
private EmbeddedChannel channel; private EmbeddedChannel channel;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
channel = new EmbeddedChannel(new StompEncoder()); channel = new EmbeddedChannel(new StompSubframeEncoder());
} }
@After @After
public void teardown() throws Exception { public void teardown() throws Exception {
Assert.assertFalse(channel.finish()); assertFalse(channel.finish());
} }
@Test @Test
public void testFrameAndContentEncoding() { public void testFrameAndContentEncoding() {
StompFrame frame = new DefaultStompFrame(StompCommand.CONNECT); StompHeadersSubframe frame = new DefaultStompHeadersSubframe(StompCommand.CONNECT);
StompHeaders headers = frame.headers(); StompHeaders headers = frame.headers();
headers.set(StompHeaders.ACCEPT_VERSION, "1.1,1.2"); headers.set(StompHeaders.ACCEPT_VERSION, "1.1,1.2");
headers.set(StompHeaders.HOST, "stomp.github.org"); headers.set(StompHeaders.HOST, "stomp.github.org");
channel.writeOutbound(frame); channel.writeOutbound(frame);
channel.writeOutbound(LastStompContent.EMPTY_LAST_CONTENT); channel.writeOutbound(LastStompContentSubframe.EMPTY_LAST_CONTENT);
ByteBuf aggregatedBuffer = Unpooled.buffer(); ByteBuf aggregatedBuffer = Unpooled.buffer();
ByteBuf byteBuf = channel.readOutbound(); ByteBuf byteBuf = channel.readOutbound();
Assert.assertNotNull(byteBuf); assertNotNull(byteBuf);
aggregatedBuffer.writeBytes(byteBuf); aggregatedBuffer.writeBytes(byteBuf);
byteBuf = channel.readOutbound(); byteBuf = channel.readOutbound();
Assert.assertNotNull(byteBuf); assertNotNull(byteBuf);
aggregatedBuffer.writeBytes(byteBuf); aggregatedBuffer.writeBytes(byteBuf);
aggregatedBuffer.resetReaderIndex(); aggregatedBuffer.resetReaderIndex();
String content = aggregatedBuffer.toString(CharsetUtil.UTF_8); String content = aggregatedBuffer.toString(CharsetUtil.UTF_8);
Assert.assertEquals(StompTestConstants.CONNECT_FRAME, content); assertEquals(StompTestConstants.CONNECT_FRAME, content);
} }
} }

View File

@ -21,7 +21,7 @@ public final class StompTestConstants {
"host:stomp.github.org\r\n" + "host:stomp.github.org\r\n" +
"accept-version:1.1,1.2\r\n" + "accept-version:1.1,1.2\r\n" +
"\r\n" + "\r\n" +
"\0"; '\0';
public static final String CONNECTED_FRAME = public static final String CONNECTED_FRAME =
"CONNECTED\r\n" + "CONNECTED\r\n" +
"version:1.2\n" + "version:1.2\n" +
@ -43,6 +43,5 @@ public final class StompTestConstants {
"hello, queue a!!!" + "hello, queue a!!!" +
"\0\n"; "\0\n";
private StompTestConstants() { private StompTestConstants() { }
}
} }

View File

@ -16,148 +16,49 @@
package io.netty.example.stomp; package io.netty.example.stomp;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.stomp.FullStompFrame; import io.netty.handler.codec.stomp.StompSubframeAggregator;
import io.netty.handler.codec.stomp.DefaultFullStompFrame; import io.netty.handler.codec.stomp.StompSubframeDecoder;
import io.netty.handler.codec.stomp.StompCommand; import io.netty.handler.codec.stomp.StompSubframeEncoder;
import io.netty.handler.codec.stomp.StompHeaders;
import io.netty.handler.codec.stomp.StompEncoder;
import io.netty.handler.codec.stomp.StompDecoder;
import io.netty.handler.codec.stomp.StompAggregator;
/** /**
* very simple stomp client implementation example, requires running stomp server to actually work * very simple stomp client implementation example, requires running stomp server to actually work
* uses default username/password and destination values from hornetq message broker * uses default username/password and destination values from hornetq message broker
*/ */
public class StompClient implements StompFrameListener { public final class StompClient {
public static final String DEAFULT_HOST = "localhost";
public static final int DEFAULT_PORT = 61613;
public static final String DEFAULT_USERNAME = "guest";
public static final String DEFAULT_PASSWORD = "guest";
private static final String EXAMPLE_TOPIC = "jms.topic.exampleTopic";
private final String host; static final boolean SSL = System.getProperty("ssl") != null;
private final int port; static final String HOST = System.getProperty("host", "127.0.0.1");
private final String username; static final int PORT = Integer.parseInt(System.getProperty("port", "61613"));
private final String password; static final String LOGIN = System.getProperty("login", "guest");
private ClientState state = ClientState.CONNECTING; static final String PASSCODE = System.getProperty("passcode", "guest");
private Channel ch; static final String TOPIC = System.getProperty("topic", "jms.topic.exampleTopic");
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
String host;
int port;
String username;
String password;
if (args.length == 0) {
host = DEAFULT_HOST;
port = DEFAULT_PORT;
username = DEFAULT_USERNAME;
password = DEFAULT_PASSWORD;
} else if (args.length == 4) {
host = args[0];
port = Integer.parseInt(args[1]);
username = args[2];
password = args[3];
} else {
System.err.println("Usage: " + StompClient.class.getSimpleName() + " <host> <port> <username> <password>");
return;
}
StompClient stompClient = new StompClient(host, port, username, password);
stompClient.run();
}
public StompClient(String host, int port, String username, String password) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
}
public void run() throws Exception {
EventLoopGroup group = new NioEventLoopGroup(); EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap(); Bootstrap b = new Bootstrap();
final StompClient that = this; b.group(group).channel(NioSocketChannel.class);
b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() { b.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception { protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new StompDecoder()); pipeline.addLast("decoder", new StompSubframeDecoder());
pipeline.addLast("encoder", new StompEncoder()); pipeline.addLast("encoder", new StompSubframeEncoder());
pipeline.addLast("aggregator", new StompAggregator(1048576)); pipeline.addLast("aggregator", new StompSubframeAggregator(1048576));
pipeline.addLast("handler", new StompClientHandler(that)); pipeline.addLast("handler", new StompClientHandler());
} }
}); });
b.remoteAddress(host, port);
this.ch = b.connect().sync().channel(); b.connect(HOST, PORT).sync().channel().closeFuture().sync();
} finally {
FullStompFrame connFrame = new DefaultFullStompFrame(StompCommand.CONNECT); group.shutdownGracefully();
connFrame.headers().set(StompHeaders.ACCEPT_VERSION, "1.2");
connFrame.headers().set(StompHeaders.HOST, host);
connFrame.headers().set(StompHeaders.LOGIN, username);
connFrame.headers().set(StompHeaders.PASSCODE, password);
ch.writeAndFlush(connFrame).sync();
}
@Override
public void onFrame(FullStompFrame frame) {
String subscrReceiptId = "001";
String disconReceiptId = "002";
try {
switch (frame.command()) {
case CONNECTED:
FullStompFrame subscribeFrame = new DefaultFullStompFrame(StompCommand.SUBSCRIBE);
subscribeFrame.headers().set(StompHeaders.DESTINATION, EXAMPLE_TOPIC);
subscribeFrame.headers().set(StompHeaders.RECEIPT, subscrReceiptId);
subscribeFrame.headers().set(StompHeaders.ID, "1");
System.out.println("connected, sending subscribe frame: " + subscribeFrame);
state = ClientState.CONNECTED;
ch.writeAndFlush(subscribeFrame);
break;
case RECEIPT:
String receiptHeader = frame.headers().get(StompHeaders.RECEIPT_ID);
if (state == ClientState.CONNECTED && receiptHeader.equals(subscrReceiptId)) {
FullStompFrame msgFrame = new DefaultFullStompFrame(StompCommand.SEND);
msgFrame.headers().set(StompHeaders.DESTINATION, EXAMPLE_TOPIC);
msgFrame.content().writeBytes("some payload".getBytes());
System.out.println("subscribed, sending message frame: " + msgFrame);
state = ClientState.SUBSCRIBED;
ch.writeAndFlush(msgFrame);
} else if (state == ClientState.DISCONNECTING && receiptHeader.equals(disconReceiptId)) {
System.out.println("disconnected, exiting..");
System.exit(0);
} else {
throw new IllegalStateException("received: " + frame + ", while internal state is " + state);
}
break;
case MESSAGE:
if (state == ClientState.SUBSCRIBED) {
System.out.println("received frame: " + frame);
FullStompFrame disconnFrame = new DefaultFullStompFrame(StompCommand.DISCONNECT);
disconnFrame.headers().set(StompHeaders.RECEIPT, disconReceiptId);
System.out.println("sending disconnect frame: " + disconnFrame);
state = ClientState.DISCONNECTING;
ch.writeAndFlush(disconnFrame);
}
break;
default:
break;
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
enum ClientState {
CONNECTING,
CONNECTED,
SUBSCRIBED,
DISCONNECTING
}
} }

View File

@ -17,23 +17,84 @@ package io.netty.example.stomp;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.stomp.FullStompFrame; import io.netty.handler.codec.stomp.DefaultStompFrame;
import io.netty.handler.codec.stomp.StompCommand;
import io.netty.handler.codec.stomp.StompFrame;
import io.netty.handler.codec.stomp.StompHeaders;
/** /**
* STOMP client inbound handler implementation, which just passes received messages to listener * STOMP client inbound handler implementation, which just passes received messages to listener
*/ */
public class StompClientHandler extends SimpleChannelInboundHandler<FullStompFrame> { public class StompClientHandler extends SimpleChannelInboundHandler<StompFrame> {
private final StompFrameListener listener;
public StompClientHandler(StompFrameListener listener) { private enum ClientState {
if (listener == null) { AUTHENTICATING,
throw new NullPointerException("listener"); AUTHENTICATED,
SUBSCRIBED,
DISCONNECTING
} }
this.listener = listener;
private ClientState state;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
state = ClientState.AUTHENTICATING;
StompFrame connFrame = new DefaultStompFrame(StompCommand.CONNECT);
connFrame.headers().set(StompHeaders.ACCEPT_VERSION, "1.2");
connFrame.headers().set(StompHeaders.HOST, StompClient.HOST);
connFrame.headers().set(StompHeaders.LOGIN, StompClient.LOGIN);
connFrame.headers().set(StompHeaders.PASSCODE, StompClient.PASSCODE);
ctx.writeAndFlush(connFrame);
} }
@Override @Override
protected void messageReceived(ChannelHandlerContext ctx, FullStompFrame msg) throws Exception { protected void messageReceived(ChannelHandlerContext ctx, StompFrame frame) throws Exception {
listener.onFrame(msg); String subscrReceiptId = "001";
String disconReceiptId = "002";
switch (frame.command()) {
case CONNECTED:
StompFrame subscribeFrame = new DefaultStompFrame(StompCommand.SUBSCRIBE);
subscribeFrame.headers().set(StompHeaders.DESTINATION, StompClient.TOPIC);
subscribeFrame.headers().set(StompHeaders.RECEIPT, subscrReceiptId);
subscribeFrame.headers().set(StompHeaders.ID, "1");
System.out.println("connected, sending subscribe frame: " + subscribeFrame);
state = ClientState.AUTHENTICATED;
ctx.writeAndFlush(subscribeFrame);
break;
case RECEIPT:
String receiptHeader = frame.headers().get(StompHeaders.RECEIPT_ID);
if (state == ClientState.AUTHENTICATED && receiptHeader.equals(subscrReceiptId)) {
StompFrame msgFrame = new DefaultStompFrame(StompCommand.SEND);
msgFrame.headers().set(StompHeaders.DESTINATION, StompClient.TOPIC);
msgFrame.content().writeBytes("some payload".getBytes());
System.out.println("subscribed, sending message frame: " + msgFrame);
state = ClientState.SUBSCRIBED;
ctx.writeAndFlush(msgFrame);
} else if (state == ClientState.DISCONNECTING && receiptHeader.equals(disconReceiptId)) {
System.out.println("disconnected");
ctx.close();
} else {
throw new IllegalStateException("received: " + frame + ", while internal state is " + state);
}
break;
case MESSAGE:
if (state == ClientState.SUBSCRIBED) {
System.out.println("received frame: " + frame);
StompFrame disconnFrame = new DefaultStompFrame(StompCommand.DISCONNECT);
disconnFrame.headers().set(StompHeaders.RECEIPT, disconReceiptId);
System.out.println("sending disconnect frame: " + disconnFrame);
state = ClientState.DISCONNECTING;
ctx.writeAndFlush(disconnFrame);
}
break;
default:
break;
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
} }
} }

View File

@ -31,6 +31,7 @@ EXAMPLE_MAP=(
'proxy-server:io.netty.example.proxy.HexDumpProxy' 'proxy-server:io.netty.example.proxy.HexDumpProxy'
'socksproxy-server:io.netty.example.socksproxy.SocksServer' 'socksproxy-server:io.netty.example.socksproxy.SocksServer'
'memcache-binary-client:io.netty.example.memcache.binary.MemcacheClient' 'memcache-binary-client:io.netty.example.memcache.binary.MemcacheClient'
'stomp-client:io.netty.example.stomp.StompClient'
'uptime-client:io.netty.example.uptime.UptimeClient' 'uptime-client:io.netty.example.uptime.UptimeClient'
'sctpecho-client:io.netty.example.sctp.SctpEchoClient' 'sctpecho-client:io.netty.example.sctp.SctpEchoClient'
'sctpecho-server:io.netty.example.sctp.SctpEchoServer' 'sctpecho-server:io.netty.example.sctp.SctpEchoServer'