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.jboss.netty.handler.codec.http.websocketx;
17
18 import org.jboss.netty.channel.Channel;
19 import org.jboss.netty.channel.ChannelFuture;
20 import org.jboss.netty.channel.ChannelFutureListener;
21 import org.jboss.netty.channel.ChannelHandlerContext;
22 import org.jboss.netty.channel.Channels;
23 import org.jboss.netty.handler.codec.http.HttpRequest;
24 import org.jboss.netty.util.internal.StringUtil;
25
26 import java.util.Collections;
27 import java.util.LinkedHashSet;
28 import java.util.Set;
29
30 /**
31 * Base class for server side web socket opening and closing handshakes
32 */
33 public abstract class WebSocketServerHandshaker {
34
35 private final String webSocketUrl;
36
37 private final String[] subprotocols;
38
39 private final WebSocketVersion version;
40
41 private final long maxFramePayloadLength;
42
43 private String selectedSubprotocol;
44
45 /**
46 * {@link ChannelFutureListener} which will call
47 * {@link Channels#fireExceptionCaught(ChannelHandlerContext, Throwable)}
48 * if the {@link ChannelFuture} was not successful.
49 */
50 public static final ChannelFutureListener HANDSHAKE_LISTENER = new ChannelFutureListener() {
51 public void operationComplete(ChannelFuture future) throws Exception {
52 if (!future.isSuccess()) {
53 Channels.fireExceptionCaught(future.getChannel(), future.getCause());
54 }
55 }
56 };
57
58 /**
59 * Constructor using default values
60 *
61 * @param version
62 * the protocol version
63 * @param webSocketUrl
64 * URL for web socket communications. e.g
65 * "ws://myhost.com/mypath". Subsequent web socket frames will be
66 * sent to this URL.
67 * @param subprotocols
68 * CSV of supported protocols. Null if sub protocols not
69 * supported.
70 */
71 protected WebSocketServerHandshaker(WebSocketVersion version, String webSocketUrl, String subprotocols) {
72 this(version, webSocketUrl, subprotocols, Long.MAX_VALUE);
73 }
74
75 /**
76 * Constructor specifying the destination web socket location
77 *
78 * @param version
79 * the protocol version
80 * @param webSocketUrl
81 * URL for web socket communications. e.g
82 * "ws://myhost.com/mypath". Subsequent web socket frames will be
83 * sent to this URL.
84 * @param subprotocols
85 * CSV of supported protocols. Null if sub protocols not
86 * supported.
87 * @param maxFramePayloadLength
88 * Maximum length of a frame's payload
89 */
90 protected WebSocketServerHandshaker(WebSocketVersion version, String webSocketUrl, String subprotocols,
91 long maxFramePayloadLength) {
92 this.version = version;
93 this.webSocketUrl = webSocketUrl;
94 if (subprotocols != null) {
95 String[] subprotocolArray = StringUtil.split(subprotocols, ',');
96 for (int i = 0; i < subprotocolArray.length; i++) {
97 subprotocolArray[i] = subprotocolArray[i].trim();
98 }
99 this.subprotocols = subprotocolArray;
100 } else {
101 this.subprotocols = new String[0];
102 }
103 this.maxFramePayloadLength = maxFramePayloadLength;
104 }
105
106 /**
107 * Returns the URL of the web socket
108 */
109 public String getWebSocketUrl() {
110 return webSocketUrl;
111 }
112
113 /**
114 * Returns the CSV of supported sub protocols
115 */
116 public Set<String> getSubprotocols() {
117 Set<String> ret = new LinkedHashSet<String>();
118 Collections.addAll(ret, subprotocols);
119 return ret;
120 }
121
122 /**
123 * Returns the version of the specification being supported
124 */
125 public WebSocketVersion getVersion() {
126 return version;
127 }
128
129 /**
130 * Returns the max length for any frame's payload
131 */
132 public long getMaxFramePayloadLength() {
133 return maxFramePayloadLength;
134 }
135
136 /**
137 * Performs the opening handshake
138 *
139 * @param channel
140 * Channel
141 * @param req
142 * HTTP Request
143 */
144 public abstract ChannelFuture handshake(Channel channel, HttpRequest req);
145
146 /**
147 * Performs the closing handshake
148 *
149 * @param channel
150 * Channel
151 * @param frame
152 * Closing Frame that was received
153 */
154 public abstract ChannelFuture close(Channel channel, CloseWebSocketFrame frame);
155
156 /**
157 * Selects the first matching supported sub protocol
158 *
159 * @param requestedSubprotocols
160 * CSV of protocols to be supported. e.g. "chat, superchat"
161 * @return First matching supported sub protocol. Null if not found.
162 */
163 protected String selectSubprotocol(String requestedSubprotocols) {
164 if (requestedSubprotocols == null || subprotocols.length == 0) {
165 return null;
166 }
167
168 String[] requestedSubprotocolArray = StringUtil.split(requestedSubprotocols, ',');
169 for (String p : requestedSubprotocolArray) {
170 String requestedSubprotocol = p.trim();
171
172 for (String supportedSubprotocol : subprotocols) {
173 if (requestedSubprotocol.equals(supportedSubprotocol)) {
174 return requestedSubprotocol;
175 }
176 }
177 }
178
179 // No match found
180 return null;
181 }
182
183 /**
184 * Returns the selected subprotocol. Null if no subprotocol has been selected.
185 * <p>
186 * This is only available AFTER <tt>handshake()</tt> has been called.
187 * </p>
188 */
189 public String getSelectedSubprotocol() {
190 return selectedSubprotocol;
191 }
192
193 protected void setSelectedSubprotocol(String value) {
194 selectedSubprotocol = value;
195 }
196
197 }