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