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