// We don't need to synchronize as replacing the "ws-decoder" will
 // process using the same thread.
 private void invokeOnSucces(Channel channel, WebSocketUpgradeHandler h) {
   if (!h.touchSuccess()) {
     try {
       h.onSuccess(nettyConfig.getNettyWebSocketFactory().newNettyWebSocket(channel, config));
     } catch (Exception ex) {
       logger.warn("onSuccess unexpected exception", ex);
     }
   }
 }
  @Override
  public void handle(Channel channel, NettyResponseFuture<?> future, Object e) throws Exception {

    if (e instanceof HttpResponse) {
      HttpResponse response = (HttpResponse) e;
      Channels.setAttribute(channel, new UpgradeCallback(future, channel, response));

    } else if (e instanceof WebSocketFrame) {

      final WebSocketFrame frame = (WebSocketFrame) e;
      WebSocketUpgradeHandler handler =
          WebSocketUpgradeHandler.class.cast(future.getAsyncHandler());
      NettyWebSocket webSocket = NettyWebSocket.class.cast(handler.onCompleted());
      invokeOnSucces(channel, handler);

      if (webSocket != null) {
        if (frame instanceof CloseWebSocketFrame) {
          Channels.setDiscard(channel);
          CloseWebSocketFrame closeFrame = CloseWebSocketFrame.class.cast(frame);
          webSocket.onClose(closeFrame.statusCode(), closeFrame.reasonText());
        } else {
          ByteBuf buf = frame.content();
          if (buf != null && buf.readableBytes() > 0) {
            try {
              NettyResponseBodyPart part =
                  nettyConfig
                      .getBodyPartFactory()
                      .newResponseBodyPart(buf, frame.isFinalFragment());
              handler.onBodyPartReceived(part);

              if (frame instanceof BinaryWebSocketFrame) {
                webSocket.onBinaryFragment(part);
              } else if (frame instanceof TextWebSocketFrame) {
                webSocket.onTextFragment(part);
              } else if (frame instanceof PingWebSocketFrame) {
                webSocket.onPing(part);
              } else if (frame instanceof PongWebSocketFrame) {
                webSocket.onPong(part);
              }
            } finally {
              buf.release();
            }
          }
        }
      } else {
        logger.debug("UpgradeHandler returned a null NettyWebSocket ");
      }
    } else {
      logger.error("Invalid message {}", e);
    }
  }
  @Override
  public void onClose(NettyResponseFuture<?> future) {
    logger.trace("onClose");

    try {
      WebSocketUpgradeHandler h = (WebSocketUpgradeHandler) future.getAsyncHandler();
      NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted());

      logger.trace("Connection was closed abnormally (that is, with no close frame being sent).");
      if (webSocket != null)
        webSocket.close(
            1006, "Connection was closed abnormally (that is, with no close frame being sent).");
    } catch (Throwable t) {
      logger.error("onError", t);
    }
  }
  @Override
  public void onError(NettyResponseFuture<?> future, Throwable e) {
    logger.warn("onError {}", e);

    try {
      WebSocketUpgradeHandler h = (WebSocketUpgradeHandler) future.getAsyncHandler();

      NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted());
      if (webSocket != null) {
        webSocket.onError(e.getCause());
        webSocket.close();
      }
    } catch (Throwable t) {
      logger.error("onError", t);
    }
  }
    @Override
    public void call() throws Exception {

      WebSocketUpgradeHandler handler =
          WebSocketUpgradeHandler.class.cast(future.getAsyncHandler());
      Request request = future.getRequest();

      HttpResponseStatus status =
          new NettyResponseStatus(future.getUri(), config, response, channel);
      HttpResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers());
      Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm();

      if (exitAfterProcessingFilters(channel, future, handler, status, responseHeaders)) {
        return;
      }

      future.setHttpHeaders(response.headers());
      if (exitAfterHandlingRedirect(
          channel, future, response, request, response.getStatus().code(), realm)) return;

      boolean validStatus = response.getStatus().equals(SWITCHING_PROTOCOLS);
      boolean validUpgrade = response.headers().get(HttpHeaders.Names.UPGRADE) != null;
      String connection = response.headers().get(HttpHeaders.Names.CONNECTION);
      if (connection == null)
        connection =
            response.headers().get(HttpHeaders.Names.CONNECTION.toLowerCase(Locale.ENGLISH));
      boolean validConnection = HttpHeaders.Values.UPGRADE.equalsIgnoreCase(connection);
      boolean statusReceived = handler.onStatusReceived(status) == State.UPGRADE;

      if (!statusReceived) {
        try {
          handler.onCompleted();
        } finally {
          future.done();
        }
        return;
      }

      final boolean headerOK = handler.onHeadersReceived(responseHeaders) == State.CONTINUE;
      if (!headerOK || !validStatus || !validUpgrade || !validConnection) {
        requestSender.abort(channel, future, new IOException("Invalid handshake response"));
        return;
      }

      String accept = response.headers().get(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT);
      String key =
          getAcceptKey(
              future
                  .getNettyRequest()
                  .getHttpRequest()
                  .headers()
                  .get(HttpHeaders.Names.SEC_WEBSOCKET_KEY));
      if (accept == null || !accept.equals(key)) {
        requestSender.abort(
            channel,
            future,
            new IOException(
                String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key)));
      }

      channelManager.upgradePipelineForWebSockets(channel.pipeline());

      invokeOnSucces(channel, handler);
      future.done();
      // set back the future so the protocol gets notified of frames
      Channels.setAttribute(channel, future);
    }