introducing general purpose websocket client 47/4647/2
authorMartin Bobak <mbobak@cisco.com>
Thu, 23 Jan 2014 13:56:15 +0000 (14:56 +0100)
committerMartin Bobak <mbobak@cisco.com>
Fri, 24 Jan 2014 07:32:29 +0000 (08:32 +0100)
Signed-off-by: Martin Bobak <mbobak@cisco.com>
Change-Id: I3c07d67572263b46bb02f0a58716e461606442c6

websocket/pom.xml [new file with mode: 0644]
websocket/websocket-client/pom.xml [new file with mode: 0644]
websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/WebSocketClientHandler.java [new file with mode: 0644]
websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/WebSocketIClient.java [new file with mode: 0644]
websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/callback/ClientMessageCallback.java [new file with mode: 0644]
websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/WebSocketClientTest.java [new file with mode: 0644]
websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServer.java [new file with mode: 0644]
websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServerHandler.java [new file with mode: 0644]
websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServerInitializer.java [new file with mode: 0644]
websocket/websocket-client/src/test/resources/logback.xml [new file with mode: 0644]

diff --git a/websocket/pom.xml b/websocket/pom.xml
new file mode 100644 (file)
index 0000000..eeea477
--- /dev/null
@@ -0,0 +1,46 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.opendaylight.yangtools</groupId>
+    <artifactId>websocket-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-all</artifactId>
+                <version>4.0.10.Final</version>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-api</artifactId>
+                <version>1.7.2</version>
+            </dependency>
+            <dependency>
+                <groupId>junit</groupId>
+                <artifactId>junit</artifactId>
+                <version>4.11</version>
+            </dependency>
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-handler</artifactId>
+                <version>4.0.10.Final</version>
+            </dependency>
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-transport</artifactId>
+                <version>4.0.10.Final</version>
+            </dependency>
+            <dependency>
+                <groupId>org.opendaylight.yangtools</groupId>
+                <artifactId>websocket-server</artifactId>
+                <version>1.0-SNAPSHOT</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+    <modules>
+        <module>websocket-client</module>
+    </modules>
+</project>
diff --git a/websocket/websocket-client/pom.xml b/websocket/websocket-client/pom.xml
new file mode 100644 (file)
index 0000000..4cb09b8
--- /dev/null
@@ -0,0 +1,43 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>websocket-client</artifactId>
+
+    <parent>
+        <groupId>org.opendaylight.yangtools</groupId>
+        <artifactId>websocket-parent</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <packaging>jar</packaging>
+    <version>1.0-SNAPSHOT</version>
+    <name>websocket-client</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-handler</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>websocket-server</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/WebSocketClientHandler.java b/websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/WebSocketClientHandler.java
new file mode 100644 (file)
index 0000000..7eeaf85
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.websocket.client;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+import io.netty.util.CharsetUtil;
+import org.opendaylight.controller.websocket.client.callback.ClientMessageCallback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
+
+    private static final Logger logger = LoggerFactory.getLogger(WebSocketClientHandler.class.toString());
+    private final WebSocketClientHandshaker handshaker;
+    private ChannelPromise handshakeFuture;
+    private ClientMessageCallback messageListener;
+
+
+    public WebSocketClientHandler(WebSocketClientHandshaker handshaker,ClientMessageCallback listener) {
+        this.handshaker = handshaker;
+        this.messageListener = listener;
+    }
+
+    public ChannelFuture handshakeFuture() {
+        return handshakeFuture;
+    }
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+        handshakeFuture = ctx.newPromise();
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        handshaker.handshake(ctx.channel());
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        logger.info("WebSocket Client disconnected!");
+    }
+
+    @Override
+    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
+        Channel ch = ctx.channel();
+        if (!handshaker.isHandshakeComplete()) {
+            handshaker.finishHandshake(ch, (FullHttpResponse) msg);
+            logger.info("WebSocket Client connected!");
+            handshakeFuture.setSuccess();
+            return;
+        }
+
+        if (msg instanceof FullHttpResponse) {
+            FullHttpResponse response = (FullHttpResponse) msg;
+            throw new RuntimeException("Unexpected FullHttpResponse (getStatus=" + response.getStatus() + ", content="
+                    + response.content().toString(CharsetUtil.UTF_8) + ')');
+        }
+
+        messageListener.onMessageReceived(msg);
+        WebSocketFrame frame = (WebSocketFrame) msg;
+
+        if (frame instanceof TextWebSocketFrame) {
+            TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
+            logger.info("WebSocket Client received message: " + textFrame.text());
+        } else if (frame instanceof PongWebSocketFrame) {
+            logger.info("WebSocket Client received pong");
+        } else if (frame instanceof CloseWebSocketFrame) {
+            logger.info("WebSocket Client received closing");
+            ch.close();
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        if (!handshakeFuture.isDone()) {
+            handshakeFuture.setFailure(cause);
+        }
+        ctx.close();
+    }
+}
+
diff --git a/websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/WebSocketIClient.java b/websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/WebSocketIClient.java
new file mode 100644 (file)
index 0000000..92aea6b
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.websocket.client;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
+import io.netty.handler.codec.http.websocketx.WebSocketVersion;
+import java.net.URI;
+import org.opendaylight.controller.websocket.client.callback.ClientMessageCallback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WebSocketIClient  {
+
+    private final URI uri;
+    private Bootstrap bootstrap = new Bootstrap();;
+    private final WebSocketClientHandler clientHandler;
+    private static final Logger logger = LoggerFactory.getLogger(WebSocketIClient.class);
+    private Channel clientChannel;
+    private final EventLoopGroup group = new NioEventLoopGroup();
+
+
+    public WebSocketIClient(URI uri,ClientMessageCallback clientMessageCallback) {
+        this.uri = uri;
+        clientHandler = new WebSocketClientHandler(
+                WebSocketClientHandshakerFactory.newHandshaker(
+                        uri, WebSocketVersion.V13, null, false,null),clientMessageCallback); // last null could be replaced with DefaultHttpHeaders
+        initialize();
+    }
+    private void initialize(){
+
+        String protocol = uri.getScheme();
+        if (!"ws".equals(protocol)) {
+            throw new IllegalArgumentException("Unsupported protocol: " + protocol);
+        }
+
+        bootstrap.group(group)
+                .channel(NioSocketChannel.class)
+                .handler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    public void initChannel(SocketChannel ch) throws Exception {
+                        ChannelPipeline pipeline = ch.pipeline();
+                        pipeline.addLast("http-codec", new HttpClientCodec());
+                        pipeline.addLast("aggregator", new HttpObjectAggregator(8192));
+                        pipeline.addLast("ws-handler", clientHandler);
+                    }
+                });
+    }
+    public void connect() throws InterruptedException{
+        clientChannel  = bootstrap.connect(uri.getHost(), uri.getPort()).sync().channel();
+        clientHandler.handshakeFuture().sync();
+    }
+
+    public void writeAndFlush(String message){
+        clientChannel.writeAndFlush(new TextWebSocketFrame(message));
+    }
+    public void writeAndFlush(Object message){
+        clientChannel.writeAndFlush(message);
+    }
+
+    public void ping(){
+        clientChannel.writeAndFlush(new PingWebSocketFrame(Unpooled.copiedBuffer(new byte[]{1, 2, 3, 4, 5, 6})));
+    }
+
+    public void close() throws InterruptedException {
+        clientChannel.writeAndFlush(new CloseWebSocketFrame());
+
+        // WebSocketClientHandler will close the connection when the server
+        // responds to the CloseWebSocketFrame.
+        clientChannel.closeFuture().sync();
+        group.shutdownGracefully();
+    }
+
+}
diff --git a/websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/callback/ClientMessageCallback.java b/websocket/websocket-client/src/main/java/org/yangtools/controller/websocket/client/callback/ClientMessageCallback.java
new file mode 100644 (file)
index 0000000..5abd77b
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.websocket.client.callback;
+
+public interface ClientMessageCallback {
+
+    public void onMessageReceived(Object message);
+}
diff --git a/websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/WebSocketClientTest.java b/websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/WebSocketClientTest.java
new file mode 100644 (file)
index 0000000..90c16ca
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.websocket;
+
+import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import java.net.URI;
+import java.net.URISyntaxException;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.websocket.client.WebSocketIClient;
+import org.opendaylight.yangtools.websocket.server.WebSocketServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WebSocketClientTest {
+    private static final Logger logger = LoggerFactory.getLogger(WebSocketClientTest.class.toString());
+    private static final String MESSAGE = "Take me to your leader!";
+    private static final int port = 8080;
+    private Thread webSocketServerThread;
+
+
+    @Before
+    public void startWebSocketServer(){
+        try {
+            WebSocketServer webSocketServer = new WebSocketServer(port);
+            webSocketServerThread = new Thread(webSocketServer);
+            webSocketServerThread.setDaemon(false);
+            webSocketServerThread.start();
+        } catch (Exception e) {
+            logger.trace("Error starting websocket server");
+        }
+    }
+    @Test
+    public void connectAndSendData(){
+
+        URI uri = null;
+        try {
+            uri = new URI("ws://localhost:8080/websocket");
+            ClientMessageCallback messageCallback = new ClientMessageCallback();
+            WebSocketIClient wsClient = new WebSocketIClient(uri,messageCallback);
+            try {
+                wsClient.connect();
+                wsClient.writeAndFlush(MESSAGE);
+                wsClient.writeAndFlush(new CloseWebSocketFrame());
+                webSocketServerThread.interrupt();
+            } catch (InterruptedException e) {
+                logger.info("WebSocket client couldn't connect to : "+uri);
+            }
+        } catch (URISyntaxException e) {
+            logger.info("There is an error in URL sytnax {}",e);
+        }
+    }
+
+    private class ClientMessageCallback implements org.opendaylight.controller.websocket.client.callback.ClientMessageCallback {
+        @Override
+        public void onMessageReceived(Object message) {
+           logger.info("received message {}",message);
+           System.out.println("received message : " + message);
+        }
+    }
+}
diff --git a/websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServer.java b/websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServer.java
new file mode 100644 (file)
index 0000000..fb6b8ca
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 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 org.opendaylight.yangtools.websocket.server;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A HTTP server which serves Web Socket requests at:
+ *
+ * http://localhost:8080/websocket
+ *
+ * Open your browser at http://localhost:8080/, then the demo page will be loaded and a Web Socket connection will be
+ * made automatically.
+ *
+ * This server illustrates support for the different web socket specification versions and will work with:
+ *
+ * <ul>
+ * <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
+ * <li>Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00)
+ * <li>Chrome 14+ (draft-ietf-hybi-thewebsocketprotocol-10)
+ * <li>Chrome 16+ (RFC 6455 aka draft-ietf-hybi-thewebsocketprotocol-17)
+ * <li>Firefox 7+ (draft-ietf-hybi-thewebsocketprotocol-10)
+ * <li>Firefox 11+ (RFC 6455 aka draft-ietf-hybi-thewebsocketprotocol-17)
+ * </ul>
+ */
+public class WebSocketServer implements Runnable {
+
+    private final int port;
+    private final ServerBootstrap bootstrap = new ServerBootstrap();
+    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
+    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
+    private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class.toString());
+
+    public WebSocketServer(int port) {
+        this.port = port;
+    }
+
+    public void run(){
+        try {
+            startServer();
+        } catch (Exception e) {
+            logger.info("Exception occured while starting webSocket server {}",e);
+        }
+    }
+    public void startServer() throws Exception {
+        try {
+            bootstrap.group(bossGroup, workerGroup)
+             .channel(NioServerSocketChannel.class)
+             .childHandler(new WebSocketServerInitializer());
+
+            Channel ch = bootstrap.bind(port).sync().channel();
+            logger.info("Web socket server started at port " + port + '.');
+            logger.info("Open your browser and navigate to http://localhost:" + port + '/');
+
+            ch.closeFuture().sync();
+        } finally {
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
+        }
+    }
+
+
+}
diff --git a/websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServerHandler.java b/websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServerHandler.java
new file mode 100644 (file)
index 0000000..bf1a105
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2012 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 org.opendaylight.yangtools.websocket.server;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
+import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
+import io.netty.util.CharsetUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import static io.netty.handler.codec.http.HttpHeaders.Names.HOST;
+import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
+import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
+import static io.netty.handler.codec.http.HttpMethod.GET;
+import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
+import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+
+/**
+ * Handles handshakes and messages
+ */
+public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
+    private static final Logger logger = LoggerFactory.getLogger(WebSocketServerHandler.class.getName());
+
+    private static final String WEBSOCKET_PATH = "/websocket";
+
+    private WebSocketServerHandshaker handshaker;
+
+    @Override
+    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
+        if (msg instanceof FullHttpRequest) {
+            handleHttpRequest(ctx, (FullHttpRequest) msg);
+        } else if (msg instanceof WebSocketFrame) {
+            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
+        }
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+        ctx.flush();
+    }
+
+    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
+        // Handle a bad request.
+        if (!req.getDecoderResult().isSuccess()) {
+            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
+            return;
+        }
+
+        // Allow only GET methods.
+        if (req.getMethod() != GET) {
+            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
+            return;
+        }
+
+
+        // Handshake
+        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
+                getWebSocketLocation(req), null, false);
+        handshaker = wsFactory.newHandshaker(req);
+        if (handshaker == null) {
+            WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
+        } else {
+            handshaker.handshake(ctx.channel(), req);
+        }
+    }
+
+    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
+
+        // Check for closing frame
+        if (frame instanceof CloseWebSocketFrame) {
+            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
+            return;
+        }
+        if (frame instanceof PingWebSocketFrame) {
+            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
+            return;
+        }
+        if (!(frame instanceof TextWebSocketFrame)) {
+            throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
+                    .getName()));
+        }
+
+        // Send the uppercase string back.
+        String request = ((TextWebSocketFrame) frame).text();
+        logger.debug(String.format("%s received %s", ctx.channel(), request));
+        ctx.channel().write(new TextWebSocketFrame(request.toUpperCase()));
+    }
+
+    private static void sendHttpResponse(
+            ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
+        // Generate an error page if response getStatus code is not OK (200).
+        if (res.getStatus().code() != 200) {
+            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
+            res.content().writeBytes(buf);
+            buf.release();
+            setContentLength(res, res.content().readableBytes());
+        }
+
+        // Send the response and close the connection if necessary.
+        ChannelFuture f = ctx.channel().writeAndFlush(res);
+        if (!isKeepAlive(req) || res.getStatus().code() != 200) {
+            f.addListener(ChannelFutureListener.CLOSE);
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        logger.info("Exception caught {}",cause);
+        ctx.close();
+    }
+
+    private static String getWebSocketLocation(FullHttpRequest req) {
+        return "ws://" + req.headers().get(HOST) + WEBSOCKET_PATH;
+    }
+
+
+}
diff --git a/websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServerInitializer.java b/websocket/websocket-client/src/test/java/org/opendaylight/yangtools/websocket/server/WebSocketServerInitializer.java
new file mode 100644 (file)
index 0000000..350eb8f
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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 org.opendaylight.yangtools.websocket.server;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import org.opendaylight.yangtools.websocket.server.WebSocketServerHandler;
+
+/**
+ */
+public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
+    @Override
+    public void initChannel(SocketChannel ch) throws Exception {
+        ChannelPipeline pipeline = ch.pipeline();
+        pipeline.addLast("codec-http", new HttpServerCodec());
+        pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
+        pipeline.addLast("handler", new WebSocketServerHandler());
+    }
+}
diff --git a/websocket/websocket-client/src/test/resources/logback.xml b/websocket/websocket-client/src/test/resources/logback.xml
new file mode 100644 (file)
index 0000000..9b9711a
--- /dev/null
@@ -0,0 +1,15 @@
+<configuration scan="true">
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+  <logger name="org.opendaylight.controller" level="DEBUG"/>
+
+  <root level="error">
+    <appender-ref ref="STDOUT" />
+  </root>
+
+</configuration>