@Override
  public void incomingFrame(Frame frame) {
    // Incoming frames are always non concurrent because
    // they are read and parsed with a single thread, and
    // therefore there is no need for synchronization.

    // This extension requires the RSV1 bit set only in the first frame.
    // Subsequent continuation frames don't have RSV1 set, but are compressed.
    if (frame.getType().isData()) incomingCompressed = frame.isRsv1();

    if (OpCode.isControlFrame(frame.getOpCode()) || !frame.hasPayload() || !incomingCompressed) {
      nextIncomingFrame(frame);
      return;
    }

    boolean appendTail = frame.isFin();
    ByteBuffer payload = frame.getPayload();
    int remaining = payload.remaining();
    byte[] input = new byte[remaining + (appendTail ? TAIL_BYTES.length : 0)];
    payload.get(input, 0, remaining);
    if (appendTail) System.arraycopy(TAIL_BYTES, 0, input, remaining, TAIL_BYTES.length);

    forwardIncoming(frame, decompress(input));

    if (frame.isFin()) incomingCompressed = false;
  }
  @Override
  public void incomingFrame(Frame frame) {
    if (LOG.isDebugEnabled()) {
      LOG.debug("{}.onFrame({})", websocket.getClass().getSimpleName(), frame);
    }

    try {
      onFrame(frame);

      byte opcode = frame.getOpCode();
      switch (opcode) {
        case OpCode.CLOSE:
          {
            boolean validate = true;
            CloseFrame closeframe = (CloseFrame) frame;
            CloseInfo close = new CloseInfo(closeframe, validate);

            // notify user websocket pojo
            onClose(close);

            // process handshake
            session.getConnection().getIOState().onCloseRemote(close);

            return;
          }
        case OpCode.PING:
          {
            if (LOG.isDebugEnabled()) {
              LOG.debug("PING: {}", BufferUtil.toDetailString(frame.getPayload()));
            }
            ByteBuffer pongBuf;
            if (frame.hasPayload()) {
              pongBuf = ByteBuffer.allocate(frame.getPayload().remaining());
              BufferUtil.put(frame.getPayload().slice(), pongBuf);
              BufferUtil.flipToFlush(pongBuf, 0);
            } else {
              pongBuf = ByteBuffer.allocate(0);
            }
            onPing(frame.getPayload());
            session.getRemote().sendPong(pongBuf);
            break;
          }
        case OpCode.PONG:
          {
            if (LOG.isDebugEnabled()) {
              LOG.debug("PONG: {}", BufferUtil.toDetailString(frame.getPayload()));
            }
            onPong(frame.getPayload());
            break;
          }
        case OpCode.BINARY:
          {
            onBinaryFrame(frame.getPayload(), frame.isFin());
            return;
          }
        case OpCode.TEXT:
          {
            onTextFrame(frame.getPayload(), frame.isFin());
            return;
          }
        case OpCode.CONTINUATION:
          {
            onContinuationFrame(frame.getPayload(), frame.isFin());
            return;
          }
        default:
          {
            LOG.debug("Unhandled OpCode: {}", opcode);
          }
      }
    } catch (NotUtf8Exception e) {
      terminateConnection(StatusCode.BAD_PAYLOAD, e.getMessage());
    } catch (CloseException e) {
      terminateConnection(e.getStatusCode(), e.getMessage());
    } catch (Throwable t) {
      unhandled(t);
    }
  }