@Test
  public void testPerformOpeningHandshake() {
    Channel channelMock = EasyMock.createMock(Channel.class);

    DefaultChannelPipeline pipeline = createPipeline(channelMock);
    EasyMock.expect(channelMock.pipeline()).andReturn(pipeline);

    // capture the http response in order to verify the headers
    Capture<HttpResponse> res = new Capture<HttpResponse>();
    EasyMock.expect(channelMock.write(capture(res)))
        .andReturn(new DefaultChannelFuture(channelMock, true));

    replay(channelMock);

    HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
    req.setHeader(Names.HOST, "server.example.com");
    req.setHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
    req.setHeader(Names.CONNECTION, "Upgrade");
    req.setHeader(Names.SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==");
    req.setHeader(Names.SEC_WEBSOCKET_ORIGIN, "http://example.com");
    req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat");
    req.setHeader(Names.SEC_WEBSOCKET_VERSION, "13");
    WebSocketServerHandshaker13 handsaker =
        new WebSocketServerHandshaker13("ws://example.com/chat", "chat", false, Integer.MAX_VALUE);
    handsaker.handshake(channelMock, req);

    Assert.assertEquals(
        "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT));
    Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
  }
  /**
   * Handle the web socket handshake for the web socket specification <a href=
   * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17</a>.
   * Versions 13-17 share the same wire protocol.
   *
   * <p>Browser request to the server:
   *
   * <pre>
   * GET /chat HTTP/1.1
   * Host: server.example.com
   * Upgrade: websocket
   * Connection: Upgrade
   * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
   * Sec-WebSocket-Origin: http://example.com
   * Sec-WebSocket-Protocol: chat, superchat
   * Sec-WebSocket-Version: 13
   * </pre>
   *
   * <p>Server response:
   *
   * <pre>
   * HTTP/1.1 101 Switching Protocols
   * Upgrade: websocket
   * Connection: Upgrade
   * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
   * Sec-WebSocket-Protocol: chat
   * </pre>
   *
   * @param channel Channel
   * @param req HTTP request
   */
  @Override
  public ChannelFuture handshake(Channel channel, HttpRequest req) {

    if (logger.isDebugEnabled()) {
      logger.debug(String.format("Channel %s WS Version 13 server handshake", channel.getId()));
    }

    HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);

    String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
    if (key == null) {
      throw new WebSocketHandshakeException("not a WebSocket request: missing key");
    }
    String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
    byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
    String accept = WebSocketUtil.base64(sha1);

    if (logger.isDebugEnabled()) {
      logger.debug(
          String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept));
    }

    res.setStatus(HttpResponseStatus.SWITCHING_PROTOCOLS);
    res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
    res.addHeader(Names.CONNECTION, Names.UPGRADE);
    res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
    String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
    if (protocol != null) {
      res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
    }

    ChannelFuture future = channel.write(res);

    // Upgrade the connection and send the handshake response.
    ChannelPipeline p = channel.getPipeline();
    if (p.get(HttpChunkAggregator.class) != null) {
      p.remove(HttpChunkAggregator.class);
    }

    p.replace(
        HttpRequestDecoder.class,
        "wsdecoder",
        new WebSocket13FrameDecoder(true, allowExtensions, this.getMaxFramePayloadLength()));
    p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));

    return future;
  }
  private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
    // Allow only GET methods.
    if (req.getMethod() != GET) {
      sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
      return;
    }

    // Send the demo page.
    if (req.getUri().equals("/")) {
      HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK);

      ChannelBuffer content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req));

      res.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8");
      setContentLength(res, content.readableBytes());

      res.setContent(content);
      sendHttpResponse(ctx, req, res);
      return;
    }

    // Serve the WebSocket handshake request.
    if (req.getUri().equals(WEBSOCKET_PATH)
        && Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
        && WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {

      // Create the WebSocket handshake response.
      HttpResponse res =
          new DefaultHttpResponse(
              HTTP_1_1, new HttpResponseStatus(101, "Web Socket Protocol Handshake"));
      res.addHeader(Names.UPGRADE, WEBSOCKET);
      res.addHeader(CONNECTION, Values.UPGRADE);

      // Fill in the headers and contents depending on handshake method.
      if (req.containsHeader(SEC_WEBSOCKET_KEY1) && req.containsHeader(SEC_WEBSOCKET_KEY2)) {
        // New handshake method with a challenge:
        res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
        res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketLocation(req));
        String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL);
        if (protocol != null) {
          res.addHeader(SEC_WEBSOCKET_PROTOCOL, protocol);
        }

        // Calculate the answer of the challenge.
        String key1 = req.getHeader(SEC_WEBSOCKET_KEY1);
        String key2 = req.getHeader(SEC_WEBSOCKET_KEY2);
        int a =
            (int)
                (Long.parseLong(key1.replaceAll("[^0-9]", ""))
                    / key1.replaceAll("[^ ]", "").length());
        int b =
            (int)
                (Long.parseLong(key2.replaceAll("[^0-9]", ""))
                    / key2.replaceAll("[^ ]", "").length());
        long c = req.getContent().readLong();
        ChannelBuffer input = ChannelBuffers.buffer(16);
        input.writeInt(a);
        input.writeInt(b);
        input.writeLong(c);
        ChannelBuffer output =
            ChannelBuffers.wrappedBuffer(MessageDigest.getInstance("MD5").digest(input.array()));
        res.setContent(output);
      } else {
        // Old handshake method with no challenge:
        res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
        res.addHeader(WEBSOCKET_LOCATION, getWebSocketLocation(req));
        String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
        if (protocol != null) {
          res.addHeader(WEBSOCKET_PROTOCOL, protocol);
        }
      }

      // Upgrade the connection and send the handshake response.
      ChannelPipeline p = ctx.getChannel().getPipeline();
      p.remove("aggregator");
      p.replace("decoder", "wsdecoder", new WebSocketFrameDecoder());

      ctx.getChannel().write(res);

      p.replace("encoder", "wsencoder", new WebSocketFrameEncoder());
      return;
    }

    // Send an error page otherwise.
    sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
  }
  /**
   * Handle the web socket handshake for the web socket specification <a href=
   * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0</a> and
   * lower. This standard is really a rehash of <a
   * href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and <a
   * href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>.
   *
   * <p>Browser request to the server:
   *
   * <pre>
   * GET /demo HTTP/1.1
   * Upgrade: WebSocket
   * Connection: Upgrade
   * Host: example.com
   * Origin: http://example.com
   * Sec-WebSocket-Protocol: chat, sample
   * Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
   * Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
   *
   * ^n:ds[4U
   * </pre>
   *
   * <p>Server response:
   *
   * <pre>
   * HTTP/1.1 101 WebSocket Protocol Handshake
   * Upgrade: WebSocket
   * Connection: Upgrade
   * Sec-WebSocket-Origin: http://example.com
   * Sec-WebSocket-Location: ws://example.com/demo
   * Sec-WebSocket-Protocol: sample
   *
   * 8jKS'y:G*Co,Wxa-
   * </pre>
   *
   * @param channel Channel
   * @param req HTTP request
   */
  @Override
  public ChannelFuture handshake(Channel channel, FullHttpRequest req, ChannelPromise promise) {

    if (logger.isDebugEnabled()) {
      logger.debug(String.format("Channel %s WS Version 00 server handshake", channel.id()));
    }

    // Serve the WebSocket handshake request.
    if (!Values.UPGRADE.equalsIgnoreCase(req.headers().get(CONNECTION))
        || !WEBSOCKET.equalsIgnoreCase(req.headers().get(Names.UPGRADE))) {
      throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
    }

    // Hixie 75 does not contain these headers while Hixie 76 does
    boolean isHixie76 =
        req.headers().contains(SEC_WEBSOCKET_KEY1) && req.headers().contains(SEC_WEBSOCKET_KEY2);

    // Create the WebSocket handshake response.
    FullHttpResponse res =
        new DefaultFullHttpResponse(
            HTTP_1_1,
            new HttpResponseStatus(
                101, isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
    res.headers().add(Names.UPGRADE, WEBSOCKET);
    res.headers().add(CONNECTION, Values.UPGRADE);

    // Fill in the headers and contents depending on handshake getMethod.
    if (isHixie76) {
      // New handshake getMethod with a challenge:
      res.headers().add(SEC_WEBSOCKET_ORIGIN, req.headers().get(ORIGIN));
      res.headers().add(SEC_WEBSOCKET_LOCATION, uri());
      String subprotocols = req.headers().get(SEC_WEBSOCKET_PROTOCOL);
      if (subprotocols != null) {
        String selectedSubprotocol = selectSubprotocol(subprotocols);
        if (selectedSubprotocol == null) {
          throw new WebSocketHandshakeException(
              "Requested subprotocol(s) not supported: " + subprotocols);
        } else {
          res.headers().add(SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
          setSelectedSubprotocol(selectedSubprotocol);
        }
      }

      // Calculate the answer of the challenge.
      String key1 = req.headers().get(SEC_WEBSOCKET_KEY1);
      String key2 = req.headers().get(SEC_WEBSOCKET_KEY2);
      int a =
          (int)
              (Long.parseLong(BEGINNING_DIGIT.matcher(key1).replaceAll(""))
                  / BEGINNING_SPACE.matcher(key1).replaceAll("").length());
      int b =
          (int)
              (Long.parseLong(BEGINNING_DIGIT.matcher(key2).replaceAll(""))
                  / BEGINNING_SPACE.matcher(key2).replaceAll("").length());
      long c = req.data().readLong();
      ByteBuf input = Unpooled.buffer(16);
      input.writeInt(a);
      input.writeInt(b);
      input.writeLong(c);
      res.data().writeBytes(WebSocketUtil.md5(input.array()));
    } else {
      // Old Hixie 75 handshake getMethod with no challenge:
      res.headers().add(WEBSOCKET_ORIGIN, req.headers().get(ORIGIN));
      res.headers().add(WEBSOCKET_LOCATION, uri());
      String protocol = req.headers().get(WEBSOCKET_PROTOCOL);
      if (protocol != null) {
        res.headers().add(WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
      }
    }

    // Upgrade the connection and send the handshake response.
    channel.write(res, promise);
    promise.addListener(
        new ChannelFutureListener() {
          @Override
          public void operationComplete(ChannelFuture future) {
            ChannelPipeline p = future.channel().pipeline();
            if (p.get(HttpObjectAggregator.class) != null) {
              p.remove(HttpObjectAggregator.class);
            }
            p.replaceAndForward(
                HttpRequestDecoder.class,
                "wsdecoder",
                new WebSocket00FrameDecoder(maxFramePayloadLength()));

            p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder());
          }
        });

    return promise;
  }