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:
parent
b286079205
commit
a8143eda27
@ -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>
|
||||||
|
@ -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) +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -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() +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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
|
@ -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) +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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() { }
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
@ -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;
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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;
|
||||||
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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() { }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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'
|
||||||
|
Loading…
Reference in New Issue
Block a user