introducing general purpose websocket client
[yangtools.git] / websocket / websocket-client / src / test / java / org / opendaylight / yangtools / websocket / server / WebSocketServerHandler.java
1 /*
2  * Copyright 2012 The Netty Project
3  *
4  * The Netty Project licenses this file to you under the Apache License,
5  * version 2.0 (the "License"); you may not use this file except in compliance
6  * with the 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.opendaylight.yangtools.websocket.server;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.Unpooled;
20 import io.netty.channel.ChannelFuture;
21 import io.netty.channel.ChannelFutureListener;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.channel.SimpleChannelInboundHandler;
24 import io.netty.handler.codec.http.DefaultFullHttpResponse;
25 import io.netty.handler.codec.http.FullHttpRequest;
26 import io.netty.handler.codec.http.FullHttpResponse;
27 import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
28 import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
29 import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
30 import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
31 import io.netty.handler.codec.http.websocketx.WebSocketFrame;
32 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
33 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
34 import io.netty.util.CharsetUtil;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import static io.netty.handler.codec.http.HttpHeaders.Names.HOST;
38 import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
39 import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
40 import static io.netty.handler.codec.http.HttpMethod.GET;
41 import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
42 import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
43 import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
44
45 /**
46  * Handles handshakes and messages
47  */
48 public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
49     private static final Logger logger = LoggerFactory.getLogger(WebSocketServerHandler.class.getName());
50
51     private static final String WEBSOCKET_PATH = "/websocket";
52
53     private WebSocketServerHandshaker handshaker;
54
55     @Override
56     public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
57         if (msg instanceof FullHttpRequest) {
58             handleHttpRequest(ctx, (FullHttpRequest) msg);
59         } else if (msg instanceof WebSocketFrame) {
60             handleWebSocketFrame(ctx, (WebSocketFrame) msg);
61         }
62     }
63
64     @Override
65     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
66         ctx.flush();
67     }
68
69     private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
70         // Handle a bad request.
71         if (!req.getDecoderResult().isSuccess()) {
72             sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
73             return;
74         }
75
76         // Allow only GET methods.
77         if (req.getMethod() != GET) {
78             sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
79             return;
80         }
81
82
83         // Handshake
84         WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
85                 getWebSocketLocation(req), null, false);
86         handshaker = wsFactory.newHandshaker(req);
87         if (handshaker == null) {
88             WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
89         } else {
90             handshaker.handshake(ctx.channel(), req);
91         }
92     }
93
94     private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
95
96         // Check for closing frame
97         if (frame instanceof CloseWebSocketFrame) {
98             handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
99             return;
100         }
101         if (frame instanceof PingWebSocketFrame) {
102             ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
103             return;
104         }
105         if (!(frame instanceof TextWebSocketFrame)) {
106             throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
107                     .getName()));
108         }
109
110         // Send the uppercase string back.
111         String request = ((TextWebSocketFrame) frame).text();
112         logger.debug(String.format("%s received %s", ctx.channel(), request));
113         ctx.channel().write(new TextWebSocketFrame(request.toUpperCase()));
114     }
115
116     private static void sendHttpResponse(
117             ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
118         // Generate an error page if response getStatus code is not OK (200).
119         if (res.getStatus().code() != 200) {
120             ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
121             res.content().writeBytes(buf);
122             buf.release();
123             setContentLength(res, res.content().readableBytes());
124         }
125
126         // Send the response and close the connection if necessary.
127         ChannelFuture f = ctx.channel().writeAndFlush(res);
128         if (!isKeepAlive(req) || res.getStatus().code() != 200) {
129             f.addListener(ChannelFutureListener.CLOSE);
130         }
131     }
132
133     @Override
134     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
135         logger.info("Exception caught {}",cause);
136         ctx.close();
137     }
138
139     private static String getWebSocketLocation(FullHttpRequest req) {
140         return "ws://" + req.headers().get(HOST) + WEBSOCKET_PATH;
141     }
142
143
144 }