1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http.websocketx;
17
18
19 import io.netty.channel.ChannelFuture;
20 import io.netty.channel.ChannelFutureListener;
21 import io.netty.channel.ChannelHandlerContext;
22 import io.netty.channel.ChannelOutboundHandler;
23 import io.netty.channel.ChannelPromise;
24 import io.netty.handler.codec.MessageToMessageDecoder;
25 import io.netty.util.ReferenceCountUtil;
26 import io.netty.util.concurrent.Future;
27 import io.netty.util.concurrent.PromiseNotifier;
28
29 import java.net.SocketAddress;
30 import java.nio.channels.ClosedChannelException;
31 import java.util.List;
32 import java.util.concurrent.TimeUnit;
33
34 abstract class WebSocketProtocolHandler extends MessageToMessageDecoder<WebSocketFrame>
35 implements ChannelOutboundHandler {
36
37 private final boolean dropPongFrames;
38 private final WebSocketCloseStatus closeStatus;
39 private final long forceCloseTimeoutMillis;
40 private ChannelPromise closeSent;
41 private Future<?> forceCloseTimeoutTask;
42
43
44
45
46 WebSocketProtocolHandler() {
47 this(true);
48 }
49
50
51
52
53
54
55
56
57 WebSocketProtocolHandler(boolean dropPongFrames) {
58 this(dropPongFrames, null, WebSocketServerProtocolConfig.DEFAULT_FORCE_CLOSE_TIMEOUT_MILLIS);
59 }
60
61 WebSocketProtocolHandler(boolean dropPongFrames,
62 WebSocketCloseStatus closeStatus,
63 long forceCloseTimeoutMillis) {
64 this.dropPongFrames = dropPongFrames;
65 this.closeStatus = closeStatus;
66 this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
67 }
68
69 @Override
70 protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception {
71 if (frame instanceof PingWebSocketFrame) {
72 frame.content().retain();
73 ctx.writeAndFlush(new PongWebSocketFrame(frame.content()));
74 readIfNeeded(ctx);
75 return;
76 }
77 if (frame instanceof PongWebSocketFrame && dropPongFrames) {
78 readIfNeeded(ctx);
79 return;
80 }
81
82 out.add(frame.retain());
83 }
84
85 private static void readIfNeeded(ChannelHandlerContext ctx) {
86 if (!ctx.channel().config().isAutoRead()) {
87 ctx.read();
88 }
89 }
90
91 @Override
92 public void close(final ChannelHandlerContext ctx, final ChannelPromise promise) throws Exception {
93 if (closeStatus == null || !ctx.channel().isActive()) {
94 ctx.close(promise);
95 } else {
96 if (closeSent == null) {
97 write(ctx, new CloseWebSocketFrame(closeStatus), ctx.newPromise());
98 }
99 flush(ctx);
100 closeSent.addListener(new ChannelFutureListener() {
101 @Override
102 public void operationComplete(ChannelFuture future) {
103 ctx.close(promise);
104 }
105 });
106 }
107 }
108
109 @Override
110 public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
111 if (closeSent != null) {
112 ReferenceCountUtil.release(msg);
113 promise.setFailure(new ClosedChannelException());
114 } else if (msg instanceof CloseWebSocketFrame) {
115 closeSent(promise.unvoid());
116 ctx.write(msg).addListener(new PromiseNotifier<Void, ChannelFuture>(false, closeSent));
117 } else {
118 ctx.write(msg, promise);
119 }
120 }
121
122 @Override
123 public void flush(ChannelHandlerContext ctx) throws Exception {
124 ctx.flush();
125
126
127 if (closeSent != null) {
128 applyCloseSentTimeout(ctx);
129 }
130 }
131
132
133
134
135
136
137
138
139 void closeSent(ChannelPromise promise) {
140 if (closeSent != null) {
141
142
143
144 closeSent.addListener(new PromiseNotifier<Void, ChannelFuture>(false, promise));
145 return;
146 }
147 closeSent = promise;
148 }
149
150 void applyCloseSentTimeout(final ChannelHandlerContext ctx) {
151 if (forceCloseTimeoutTask != null || closeSent.isDone() || forceCloseTimeoutMillis < 0) {
152 return;
153 }
154
155 final Future<?> timeoutTask = ctx.executor().schedule(new Runnable() {
156 @Override
157 public void run() {
158 if (!closeSent.isDone()) {
159 closeSent.tryFailure(buildHandshakeException("send close frame timed out"));
160
161
162
163 ctx.close();
164 }
165 }
166 }, forceCloseTimeoutMillis, TimeUnit.MILLISECONDS);
167 forceCloseTimeoutTask = timeoutTask;
168
169 closeSent.addListener(new ChannelFutureListener() {
170 @Override
171 public void operationComplete(ChannelFuture future) {
172 timeoutTask.cancel(false);
173 }
174 });
175 }
176
177
178
179
180
181 protected WebSocketHandshakeException buildHandshakeException(String message) {
182 return new WebSocketHandshakeException(message);
183 }
184
185 @Override
186 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
187 ChannelPromise promise) throws Exception {
188 ctx.bind(localAddress, promise);
189 }
190
191 @Override
192 public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
193 SocketAddress localAddress, ChannelPromise promise) throws Exception {
194 ctx.connect(remoteAddress, localAddress, promise);
195 }
196
197 @Override
198 public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
199 throws Exception {
200 ctx.disconnect(promise);
201 }
202
203 @Override
204 public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
205 ctx.deregister(promise);
206 }
207
208 @Override
209 public void read(ChannelHandlerContext ctx) throws Exception {
210 ctx.read();
211 }
212
213 @Override
214 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
215 ctx.fireExceptionCaught(cause);
216 ctx.close();
217 }
218 }