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.ChannelHandlerContext;
21 import io.netty.channel.ChannelOutboundHandler;
22 import io.netty.channel.ChannelPromise;
23 import io.netty.handler.codec.MessageToMessageDecoder;
24 import io.netty.util.ReferenceCountUtil;
25 import io.netty.util.concurrent.Future;
26 import io.netty.util.concurrent.PromiseNotifier;
27
28 import java.net.SocketAddress;
29 import java.nio.channels.ClosedChannelException;
30 import java.util.List;
31 import java.util.concurrent.TimeUnit;
32
33 abstract class WebSocketProtocolHandler extends MessageToMessageDecoder<WebSocketFrame>
34 implements ChannelOutboundHandler {
35
36 private final boolean dropPongFrames;
37 private final WebSocketCloseStatus closeStatus;
38 private final long forceCloseTimeoutMillis;
39 private ChannelPromise closeSent;
40 private Future<?> forceCloseTimeoutTask;
41
42
43
44
45 WebSocketProtocolHandler() {
46 this(true);
47 }
48
49
50
51
52
53
54
55
56 WebSocketProtocolHandler(boolean dropPongFrames) {
57 this(dropPongFrames, null, WebSocketServerProtocolConfig.DEFAULT_FORCE_CLOSE_TIMEOUT_MILLIS);
58 }
59
60 WebSocketProtocolHandler(boolean dropPongFrames,
61 WebSocketCloseStatus closeStatus,
62 long forceCloseTimeoutMillis) {
63 super(WebSocketFrame.class);
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.alloc()), ctx.newPromise());
98 }
99 flush(ctx);
100 closeSent.addListener(future -> ctx.close(promise));
101 }
102 }
103
104 @Override
105 public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
106 if (closeSent != null) {
107 ReferenceCountUtil.release(msg);
108 promise.setFailure(new ClosedChannelException());
109 } else if (msg instanceof CloseWebSocketFrame) {
110 closeSent(promise.unvoid());
111 ctx.write(msg).addListener(new PromiseNotifier<Void, ChannelFuture>(false, closeSent));
112 } else {
113 ctx.write(msg, promise);
114 }
115 }
116
117 @Override
118 public void flush(ChannelHandlerContext ctx) throws Exception {
119 ctx.flush();
120
121
122 if (closeSent != null) {
123 applyCloseSentTimeout(ctx);
124 }
125 }
126
127
128
129
130
131
132
133
134 void closeSent(ChannelPromise promise) {
135 if (closeSent != null) {
136
137
138
139 closeSent.addListener(new PromiseNotifier<Void, ChannelFuture>(false, promise));
140 return;
141 }
142 closeSent = promise;
143 }
144
145 void applyCloseSentTimeout(ChannelHandlerContext ctx) {
146 if (forceCloseTimeoutTask != null || closeSent.isDone() || forceCloseTimeoutMillis < 0) {
147 return;
148 }
149
150 final Future<?> timeoutTask = ctx.executor().schedule(new Runnable() {
151 @Override
152 public void run() {
153 if (!closeSent.isDone()) {
154 closeSent.tryFailure(buildHandshakeException("send close frame timed out"));
155
156
157
158 ctx.close();
159 }
160 }
161 }, forceCloseTimeoutMillis, TimeUnit.MILLISECONDS);
162 forceCloseTimeoutTask = timeoutTask;
163
164 closeSent.addListener(future -> timeoutTask.cancel(false));
165 }
166
167
168
169
170
171 protected WebSocketHandshakeException buildHandshakeException(String message) {
172 return new WebSocketHandshakeException(message);
173 }
174
175 @Override
176 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
177 ChannelPromise promise) throws Exception {
178 ctx.bind(localAddress, promise);
179 }
180
181 @Override
182 public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
183 SocketAddress localAddress, ChannelPromise promise) throws Exception {
184 ctx.connect(remoteAddress, localAddress, promise);
185 }
186
187 @Override
188 public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
189 throws Exception {
190 ctx.disconnect(promise);
191 }
192
193 @Override
194 public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
195 ctx.deregister(promise);
196 }
197
198 @Override
199 public void read(ChannelHandlerContext ctx) throws Exception {
200 ctx.read();
201 }
202
203 @Override
204 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
205 ctx.fireExceptionCaught(cause);
206 ctx.close();
207 }
208 }