1 /* 2 * Copyright 2009 Red Hat, Inc. 3 * 4 * Red Hat licenses this file to you under the Apache License, version 2.0 5 * (the "License"); you may not use this file except in compliance with the 6 * License. You may obtain a copy of the License at: 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package org.jboss.netty.handler.codec.replay; 17 18 import java.net.SocketAddress; 19 import java.util.concurrent.atomic.AtomicReference; 20 21 import org.jboss.netty.buffer.ChannelBuffer; 22 import org.jboss.netty.buffer.ChannelBufferFactory; 23 import org.jboss.netty.buffer.ChannelBuffers; 24 import org.jboss.netty.channel.Channel; 25 import org.jboss.netty.channel.ChannelHandler; 26 import org.jboss.netty.channel.ChannelHandlerContext; 27 import org.jboss.netty.channel.ChannelPipeline; 28 import org.jboss.netty.channel.ChannelStateEvent; 29 import org.jboss.netty.channel.Channels; 30 import org.jboss.netty.channel.ExceptionEvent; 31 import org.jboss.netty.channel.MessageEvent; 32 import org.jboss.netty.channel.SimpleChannelUpstreamHandler; 33 import org.jboss.netty.handler.codec.frame.FrameDecoder; 34 35 /** 36 * A specialized variation of {@link FrameDecoder} which enables implementation 37 * of a non-blocking decoder in the blocking I/O paradigm. 38 * <p> 39 * The biggest difference between {@link ReplayingDecoder} and 40 * {@link FrameDecoder} is that {@link ReplayingDecoder} allows you to 41 * implement the {@code decode()} and {@code decodeLast()} methods just like 42 * all required bytes were received already, rather than checking the 43 * availability of the required bytes. For example, the following 44 * {@link FrameDecoder} implementation: 45 * <pre> 46 * public class IntegerHeaderFrameDecoder extends {@link FrameDecoder} { 47 * 48 * {@code @Override} 49 * protected Object decode({@link ChannelHandlerContext} ctx, 50 * {@link Channel} channel, 51 * {@link ChannelBuffer} buf) throws Exception { 52 * 53 * if (buf.readableBytes() < 4) { 54 * return <strong>null</strong>; 55 * } 56 * 57 * buf.markReaderIndex(); 58 * int length = buf.readInt(); 59 * 60 * if (buf.readableBytes() < length) { 61 * buf.resetReaderIndex(); 62 * return <strong>null</strong>; 63 * } 64 * 65 * return buf.readBytes(length); 66 * } 67 * } 68 * </pre> 69 * is simplified like the following with {@link ReplayingDecoder}: 70 * <pre> 71 * public class IntegerHeaderFrameDecoder 72 * extends {@link ReplayingDecoder}<{@link VoidEnum}> { 73 * 74 * protected Object decode({@link ChannelHandlerContext} ctx, 75 * {@link Channel} channel, 76 * {@link ChannelBuffer} buf, 77 * {@link VoidEnum} state) throws Exception { 78 * 79 * return buf.readBytes(buf.readInt()); 80 * } 81 * } 82 * </pre> 83 * 84 * <h3>How does this work?</h3> 85 * <p> 86 * {@link ReplayingDecoder} passes a specialized {@link ChannelBuffer} 87 * implementation which throws an {@link Error} of certain type when there's not 88 * enough data in the buffer. In the {@code IntegerHeaderFrameDecoder} above, 89 * you just assumed that there will be 4 or more bytes in the buffer when 90 * you call {@code buf.readInt()}. If there's really 4 bytes in the buffer, 91 * it will return the integer header as you expected. Otherwise, the 92 * {@link Error} will be raised and the control will be returned to 93 * {@link ReplayingDecoder}. If {@link ReplayingDecoder} catches the 94 * {@link Error}, then it will rewind the {@code readerIndex} of the buffer 95 * back to the 'initial' position (i.e. the beginning of the buffer) and call 96 * the {@code decode(..)} method again when more data is received into the 97 * buffer. 98 * <p> 99 * Please note that the overhead of throwing an {@link Error} is minimal unlike 100 * throwing a new {@link Exception} in an ordinary way. {@link ReplayingDecoder} 101 * reuses the same {@link Error} instance so that it does not need to fill its 102 * stack trace, which takes most of {@link Exception} initialization time. 103 * 104 * <h3>Limitations</h3> 105 * <p> 106 * At the cost of the simplicity, {@link ReplayingDecoder} enforces you a few 107 * limitations: 108 * <ul> 109 * <li>Some buffer operations are prohibited.</li> 110 * <li>Performance can be worse if the network is slow and the message 111 * format is complicated unlike the example above. In this case, your 112 * decoder might have to decode the same part of the message over and over 113 * again.</li> 114 * <li>You must keep in mind that {@code decode(..)} method can be called many 115 * times to decode a single message. For example, the following code will 116 * not work: 117 * <pre> public class MyDecoder extends {@link ReplayingDecoder}<{@link VoidEnum}> { 118 * 119 * private final Queue<Integer> values = new LinkedList<Integer>(); 120 * 121 * {@code @Override} 122 * public Object decode(.., {@link ChannelBuffer} buffer, ..) throws Exception { 123 * 124 * // A message contains 2 integers. 125 * values.offer(buffer.readInt()); 126 * values.offer(buffer.readInt()); 127 * 128 * // This assertion will fail intermittently since values.offer() 129 * // can be called more than two times! 130 * assert values.size() == 2; 131 * return values.poll() + values.poll(); 132 * } 133 * }</pre> 134 * The correct implementation looks like the following, and you can also 135 * utilize the 'checkpoint' feature which is explained in detail in the 136 * next section. 137 * <pre> public class MyDecoder extends {@link ReplayingDecoder}<{@link VoidEnum}> { 138 * 139 * private final Queue<Integer> values = new LinkedList<Integer>(); 140 * 141 * {@code @Override} 142 * public Object decode(.., {@link ChannelBuffer} buffer, ..) throws Exception { 143 * 144 * // Revert the state of the variable that might have been changed 145 * // since the last partial decode. 146 * values.clear(); 147 * 148 * // A message contains 2 integers. 149 * values.offer(buffer.readInt()); 150 * values.offer(buffer.readInt()); 151 * 152 * // Now we know this assertion will never fail. 153 * assert values.size() == 2; 154 * return values.poll() + values.poll(); 155 * } 156 * }</pre> 157 * </li> 158 * </ul> 159 * 160 * <h3>Improving the performance</h3> 161 * <p> 162 * Fortunately, the performance of a complex decoder implementation can be 163 * improved significantly with the {@code checkpoint()} method. The 164 * {@code checkpoint()} method updates the 'initial' position of the buffer so 165 * that {@link ReplayingDecoder} rewinds the {@code readerIndex} of the buffer 166 * to the last position where you called the {@code checkpoint()} method. 167 * 168 * <h4>Calling {@code checkpoint(T)} with an {@link Enum}</h4> 169 * <p> 170 * Although you can just use {@code checkpoint()} method and manage the state 171 * of the decoder by yourself, the easiest way to manage the state of the 172 * decoder is to create an {@link Enum} type which represents the current state 173 * of the decoder and to call {@code checkpoint(T)} method whenever the state 174 * changes. You can have as many states as you want depending on the 175 * complexity of the message you want to decode: 176 * 177 * <pre> 178 * public enum MyDecoderState { 179 * READ_LENGTH, 180 * READ_CONTENT; 181 * } 182 * 183 * public class IntegerHeaderFrameDecoder 184 * extends {@link ReplayingDecoder}<<strong>MyDecoderState</strong>> { 185 * 186 * private int length; 187 * 188 * public IntegerHeaderFrameDecoder() { 189 * // Set the initial state. 190 * <strong>super(MyDecoderState.READ_LENGTH);</strong> 191 * } 192 * 193 * {@code @Override} 194 * protected Object decode({@link ChannelHandlerContext} ctx, 195 * {@link Channel} channel, 196 * {@link ChannelBuffer} buf, 197 * <b>MyDecoderState</b> state) throws Exception { 198 * switch (state) { 199 * case READ_LENGTH: 200 * length = buf.readInt(); 201 * <strong>checkpoint(MyDecoderState.READ_CONTENT);</strong> 202 * case READ_CONTENT: 203 * ChannelBuffer frame = buf.readBytes(length); 204 * <strong>checkpoint(MyDecoderState.READ_LENGTH);</strong> 205 * return frame; 206 * default: 207 * throw new Error("Shouldn't reach here."); 208 * } 209 * } 210 * } 211 * </pre> 212 * 213 * <h4>Calling {@code checkpoint()} with no parameter</h4> 214 * <p> 215 * An alternative way to manage the decoder state is to manage it by yourself. 216 * <pre> 217 * public class IntegerHeaderFrameDecoder 218 * extends {@link ReplayingDecoder}<<strong>{@link VoidEnum}</strong>> { 219 * 220 * <strong>private boolean readLength;</strong> 221 * private int length; 222 * 223 * {@code @Override} 224 * protected Object decode({@link ChannelHandlerContext} ctx, 225 * {@link Channel} channel, 226 * {@link ChannelBuffer} buf, 227 * {@link VoidEnum} state) throws Exception { 228 * if (!readLength) { 229 * length = buf.readInt(); 230 * <strong>readLength = true;</strong> 231 * <strong>checkpoint();</strong> 232 * } 233 * 234 * if (readLength) { 235 * ChannelBuffer frame = buf.readBytes(length); 236 * <strong>readLength = false;</strong> 237 * <strong>checkpoint();</strong> 238 * return frame; 239 * } 240 * } 241 * } 242 * </pre> 243 * 244 * <h3>Replacing a decoder with another decoder in a pipeline</h3> 245 * <p> 246 * If you are going to write a protocol multiplexer, you will probably want to 247 * replace a {@link ReplayingDecoder} (protocol detector) with another 248 * {@link ReplayingDecoder} or {@link FrameDecoder} (actual protocol decoder). 249 * It is not possible to achieve this simply by calling 250 * {@link ChannelPipeline#replace(ChannelHandler, String, ChannelHandler)}, but 251 * some additional steps are required: 252 * <pre> 253 * public class FirstDecoder extends {@link ReplayingDecoder}<{@link VoidEnum}> { 254 * 255 * public FirstDecoder() { 256 * super(true); // Enable unfold 257 * } 258 * 259 * {@code @Override} 260 * protected Object decode({@link ChannelHandlerContext} ctx, 261 * {@link Channel} ch, 262 * {@link ChannelBuffer} buf, 263 * {@link VoidEnum} state) { 264 * ... 265 * // Decode the first message 266 * Object firstMessage = ...; 267 * 268 * // Add the second decoder 269 * ctx.getPipeline().addLast("second", new SecondDecoder()); 270 * 271 * // Remove the first decoder (me) 272 * ctx.getPipeline().remove(this); 273 * 274 * if (buf.readable()) { 275 * // Hand off the remaining data to the second decoder 276 * return new Object[] { firstMessage, buf.readBytes(<b>super.actualReadableBytes()</b>) }; 277 * } else { 278 * // Nothing to hand off 279 * return firstMessage; 280 * } 281 * } 282 * </pre> 283 * 284 * @author <a href="http://www.jboss.org/netty/">The Netty Project</a> 285 * @author <a href="http://gleamynode.net/">Trustin Lee</a> 286 * 287 * @version $Rev: 2358 $, $Date: 2010-08-30 15:03:31 +0900 (Mon, 30 Aug 2010) $ 288 * 289 * @param <T> 290 * the state type; use {@link VoidEnum} if state management is unused 291 * 292 * @apiviz.landmark 293 * @apiviz.has org.jboss.netty.handler.codec.replay.UnreplayableOperationException oneway - - throws 294 */ 295 public abstract class ReplayingDecoder<T extends Enum<T>> 296 extends SimpleChannelUpstreamHandler { 297 298 299 private final AtomicReference<ChannelBuffer> cumulation = 300 new AtomicReference<ChannelBuffer>(); 301 private final boolean unfold; 302 private ReplayingDecoderBuffer replayable; 303 private T state; 304 private int checkpoint; 305 306 /** 307 * Creates a new instance with no initial state (i.e: {@code null}). 308 */ 309 protected ReplayingDecoder() { 310 this(null); 311 } 312 313 protected ReplayingDecoder(boolean unfold) { 314 this(null, unfold); 315 } 316 317 /** 318 * Creates a new instance with the specified initial state. 319 */ 320 protected ReplayingDecoder(T initialState) { 321 this(initialState, false); 322 } 323 324 protected ReplayingDecoder(T initialState, boolean unfold) { 325 this.state = initialState; 326 this.unfold = unfold; 327 } 328 329 /** 330 * Stores the internal cumulative buffer's reader position. 331 */ 332 protected void checkpoint() { 333 ChannelBuffer cumulation = this.cumulation.get(); 334 if (cumulation != null) { 335 checkpoint = cumulation.readerIndex(); 336 } else { 337 checkpoint = -1; // buffer not available (already cleaned up) 338 } 339 } 340 341 /** 342 * Stores the internal cumulative buffer's reader position and updates 343 * the current decoder state. 344 */ 345 protected void checkpoint(T state) { 346 checkpoint(); 347 setState(state); 348 } 349 350 /** 351 * Returns the current state of this decoder. 352 * @return the current state of this decoder 353 */ 354 protected T getState() { 355 return state; 356 } 357 358 /** 359 * Sets the current state of this decoder. 360 * @return the old state of this decoder 361 */ 362 protected T setState(T newState) { 363 T oldState = state; 364 state = newState; 365 return oldState; 366 } 367 368 /** 369 * Returns the actual number of readable bytes in the internal cumulative 370 * buffer of this decoder. You usually do not need to rely on this value 371 * to write a decoder. Use it only when you muse use it at your own risk. 372 * This method is a shortcut to {@link #internalBuffer() internalBuffer().readableBytes()}. 373 */ 374 protected int actualReadableBytes() { 375 return internalBuffer().readableBytes(); 376 } 377 378 /** 379 * Returns the internal cumulative buffer of this decoder. You usually 380 * do not need to access the internal buffer directly to write a decoder. 381 * Use it only when you must use it at your own risk. 382 */ 383 protected ChannelBuffer internalBuffer() { 384 ChannelBuffer buf = cumulation.get(); 385 if (buf == null) { 386 return ChannelBuffers.EMPTY_BUFFER; 387 } 388 return buf; 389 } 390 391 /** 392 * Decodes the received packets so far into a frame. 393 * 394 * @param ctx the context of this handler 395 * @param channel the current channel 396 * @param buffer the cumulative buffer of received packets so far. 397 * Note that the buffer might be empty, which means you 398 * should not make an assumption that the buffer contains 399 * at least one byte in your decoder implementation. 400 * @param state the current decoder state ({@code null} if unused) 401 * 402 * @return the decoded frame 403 */ 404 protected abstract Object decode(ChannelHandlerContext ctx, 405 Channel channel, ChannelBuffer buffer, T state) throws Exception; 406 407 /** 408 * Decodes the received data so far into a frame when the channel is 409 * disconnected. 410 * 411 * @param ctx the context of this handler 412 * @param channel the current channel 413 * @param buffer the cumulative buffer of received packets so far. 414 * Note that the buffer might be empty, which means you 415 * should not make an assumption that the buffer contains 416 * at least one byte in your decoder implementation. 417 * @param state the current decoder state ({@code null} if unused) 418 * 419 * @return the decoded frame 420 */ 421 protected Object decodeLast( 422 ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, T state) throws Exception { 423 return decode(ctx, channel, buffer, state); 424 } 425 426 @Override 427 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) 428 throws Exception { 429 430 Object m = e.getMessage(); 431 if (!(m instanceof ChannelBuffer)) { 432 ctx.sendUpstream(e); 433 return; 434 } 435 436 ChannelBuffer input = (ChannelBuffer) m; 437 if (!input.readable()) { 438 return; 439 } 440 441 ChannelBuffer cumulation = cumulation(ctx); 442 cumulation.discardReadBytes(); 443 cumulation.writeBytes(input); 444 callDecode(ctx, e.getChannel(), cumulation, e.getRemoteAddress()); 445 } 446 447 @Override 448 public void channelDisconnected(ChannelHandlerContext ctx, 449 ChannelStateEvent e) throws Exception { 450 cleanup(ctx, e); 451 } 452 453 @Override 454 public void channelClosed(ChannelHandlerContext ctx, 455 ChannelStateEvent e) throws Exception { 456 cleanup(ctx, e); 457 } 458 459 @Override 460 public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) 461 throws Exception { 462 ctx.sendUpstream(e); 463 } 464 465 private void callDecode(ChannelHandlerContext context, Channel channel, ChannelBuffer cumulation, SocketAddress remoteAddress) throws Exception { 466 while (cumulation.readable()) { 467 int oldReaderIndex = checkpoint = cumulation.readerIndex(); 468 Object result = null; 469 T oldState = state; 470 try { 471 result = decode(context, channel, replayable, state); 472 if (result == null) { 473 if (oldReaderIndex == cumulation.readerIndex() && oldState == state) { 474 throw new IllegalStateException( 475 "null cannot be returned if no data is consumed and state didn't change."); 476 } else { 477 // Previous data has been discarded or caused state transition. 478 // Probably it is reading on. 479 continue; 480 } 481 } 482 } catch (ReplayError replay) { 483 // Return to the checkpoint (or oldPosition) and retry. 484 int checkpoint = this.checkpoint; 485 if (checkpoint >= 0) { 486 cumulation.readerIndex(checkpoint); 487 } else { 488 // Called by cleanup() - no need to maintain the readerIndex 489 // anymore because the buffer has been released already. 490 } 491 } 492 493 if (result == null) { 494 // Seems like more data is required. 495 // Let us wait for the next notification. 496 break; 497 } 498 499 if (oldReaderIndex == cumulation.readerIndex() && oldState == state) { 500 throw new IllegalStateException( 501 "decode() method must consume at least one byte " + 502 "if it returned a decoded message (caused by: " + 503 getClass() + ")"); 504 } 505 506 // A successful decode 507 unfoldAndfireMessageReceived(context, result, remoteAddress); 508 } 509 } 510 511 private void unfoldAndfireMessageReceived( 512 ChannelHandlerContext context, Object result, SocketAddress remoteAddress) { 513 if (unfold) { 514 if (result instanceof Object[]) { 515 for (Object r: (Object[]) result) { 516 Channels.fireMessageReceived(context, r, remoteAddress); 517 } 518 } else if (result instanceof Iterable<?>) { 519 for (Object r: (Iterable<?>) result) { 520 Channels.fireMessageReceived(context, r, remoteAddress); 521 } 522 } else { 523 Channels.fireMessageReceived(context, result, remoteAddress); 524 } 525 } else { 526 Channels.fireMessageReceived(context, result, remoteAddress); 527 } 528 } 529 530 private void cleanup(ChannelHandlerContext ctx, ChannelStateEvent e) 531 throws Exception { 532 try { 533 ChannelBuffer cumulation = this.cumulation.getAndSet(null); 534 if (cumulation == null) { 535 return; 536 } 537 538 replayable.terminate(); 539 540 if (cumulation.readable()) { 541 // Make sure all data was read before notifying a closed channel. 542 callDecode(ctx, e.getChannel(), cumulation, null); 543 } 544 545 // Call decodeLast() finally. Please note that decodeLast() is 546 // called even if there's nothing more to read from the buffer to 547 // notify a user that the connection was closed explicitly. 548 Object partiallyDecoded = decodeLast(ctx, e.getChannel(), replayable, state); 549 if (partiallyDecoded != null) { 550 unfoldAndfireMessageReceived(ctx, partiallyDecoded, null); 551 } 552 } catch (ReplayError replay) { 553 // Ignore 554 } finally { 555 ctx.sendUpstream(e); 556 } 557 } 558 559 private ChannelBuffer cumulation(ChannelHandlerContext ctx) { 560 ChannelBuffer buf = cumulation.get(); 561 if (buf == null) { 562 ChannelBufferFactory factory = ctx.getChannel().getConfig().getBufferFactory(); 563 buf = new UnsafeDynamicChannelBuffer(factory); 564 if (cumulation.compareAndSet(null, buf)) { 565 replayable = new ReplayingDecoderBuffer(buf); 566 } else { 567 buf = cumulation.get(); 568 } 569 } 570 return buf; 571 } 572 }