001/*
002 * Copyright 2002-2013 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.socket;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.springframework.http.HttpHeaders;
027import org.springframework.util.CollectionUtils;
028
029/**
030 * An {@link org.springframework.http.HttpHeaders} variant that adds support for
031 * the HTTP headers defined by the WebSocket specification RFC 6455.
032 *
033 * @author Rossen Stoyanchev
034 * @since 4.0
035 */
036public class WebSocketHttpHeaders extends HttpHeaders {
037
038        public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";
039
040        public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions";
041
042        public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
043
044        public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
045
046        public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
047
048        private static final long serialVersionUID = -6644521016187828916L;
049
050
051        private final HttpHeaders headers;
052
053
054        /**
055         * Create a new instance.
056         */
057        public WebSocketHttpHeaders() {
058                this(new HttpHeaders(), false);
059        }
060
061        /**
062         * Create an instance that wraps the given pre-existing HttpHeaders and also
063         * propagate all changes to it.
064         * @param headers the HTTP headers to wrap
065         */
066        public WebSocketHttpHeaders(HttpHeaders headers) {
067                this(headers, false);
068        }
069
070        /**
071         * Private constructor that can create read-only {@code WebSocketHttpHeader} instances.
072         */
073        private WebSocketHttpHeaders(HttpHeaders headers, boolean readOnly) {
074                this.headers = readOnly ? HttpHeaders.readOnlyHttpHeaders(headers) : headers;
075        }
076
077        /**
078         * Returns {@code WebSocketHttpHeaders} object that can only be read, not written to.
079         */
080        public static WebSocketHttpHeaders readOnlyWebSocketHttpHeaders(WebSocketHttpHeaders headers) {
081                return new WebSocketHttpHeaders(headers, true);
082        }
083
084
085        /**
086         * Sets the (new) value of the {@code Sec-WebSocket-Accept} header.
087         * @param secWebSocketAccept the value of the header
088         */
089        public void setSecWebSocketAccept(String secWebSocketAccept) {
090                set(SEC_WEBSOCKET_ACCEPT, secWebSocketAccept);
091        }
092
093        /**
094         * Returns the value of the {@code Sec-WebSocket-Accept} header.
095         * @return the value of the header
096         */
097        public String getSecWebSocketAccept() {
098                return getFirst(SEC_WEBSOCKET_ACCEPT);
099        }
100
101        /**
102         * Returns the value of the {@code Sec-WebSocket-Extensions} header.
103         * @return the value of the header
104         */
105        public List<WebSocketExtension> getSecWebSocketExtensions() {
106                List<String> values = get(SEC_WEBSOCKET_EXTENSIONS);
107                if (CollectionUtils.isEmpty(values)) {
108                        return Collections.emptyList();
109                }
110                else {
111                        List<WebSocketExtension> result = new ArrayList<WebSocketExtension>(values.size());
112                        for (String value : values) {
113                                result.addAll(WebSocketExtension.parseExtensions(value));
114                        }
115                        return result;
116                }
117        }
118
119        /**
120         * Sets the (new) value(s) of the {@code Sec-WebSocket-Extensions} header.
121         * @param extensions the values for the header
122         */
123        public void setSecWebSocketExtensions(List<WebSocketExtension> extensions) {
124                List<String> result = new ArrayList<String>(extensions.size());
125                for (WebSocketExtension extension : extensions) {
126                        result.add(extension.toString());
127                }
128                set(SEC_WEBSOCKET_EXTENSIONS, toCommaDelimitedString(result));
129        }
130
131        /**
132         * Sets the (new) value of the {@code Sec-WebSocket-Key} header.
133         * @param secWebSocketKey the value of the header
134         */
135        public void setSecWebSocketKey(String secWebSocketKey) {
136                set(SEC_WEBSOCKET_KEY, secWebSocketKey);
137        }
138
139        /**
140         * Returns the value of the {@code Sec-WebSocket-Key} header.
141         * @return the value of the header
142         */
143        public String getSecWebSocketKey() {
144                return getFirst(SEC_WEBSOCKET_KEY);
145        }
146
147        /**
148         * Sets the (new) value of the {@code Sec-WebSocket-Protocol} header.
149         * @param secWebSocketProtocol the value of the header
150         */
151        public void setSecWebSocketProtocol(String secWebSocketProtocol) {
152                if (secWebSocketProtocol != null) {
153                        set(SEC_WEBSOCKET_PROTOCOL, secWebSocketProtocol);
154                }
155        }
156
157        /**
158         * Sets the (new) value of the {@code Sec-WebSocket-Protocol} header.
159         * @param secWebSocketProtocols the value of the header
160         */
161        public void setSecWebSocketProtocol(List<String> secWebSocketProtocols) {
162                set(SEC_WEBSOCKET_PROTOCOL, toCommaDelimitedString(secWebSocketProtocols));
163        }
164
165        /**
166         * Returns the value of the {@code Sec-WebSocket-Key} header.
167         * @return the value of the header
168         */
169        public List<String> getSecWebSocketProtocol() {
170                List<String> values = get(SEC_WEBSOCKET_PROTOCOL);
171                if (CollectionUtils.isEmpty(values)) {
172                        return Collections.emptyList();
173                }
174                else if (values.size() == 1) {
175                        return getValuesAsList(SEC_WEBSOCKET_PROTOCOL);
176                }
177                else {
178                        return values;
179                }
180        }
181
182        /**
183         * Sets the (new) value of the {@code Sec-WebSocket-Version} header.
184         * @param secWebSocketVersion the value of the header
185         */
186        public void setSecWebSocketVersion(String secWebSocketVersion) {
187                set(SEC_WEBSOCKET_VERSION, secWebSocketVersion);
188        }
189
190        /**
191         * Returns the value of the {@code Sec-WebSocket-Version} header.
192         * @return the value of the header
193         */
194        public String getSecWebSocketVersion() {
195                return getFirst(SEC_WEBSOCKET_VERSION);
196        }
197
198
199        // Single string methods
200
201        /**
202         * Return the first header value for the given header name, if any.
203         * @param headerName the header name
204         * @return the first header value; or {@code null}
205         */
206        @Override
207        public String getFirst(String headerName) {
208                return this.headers.getFirst(headerName);
209        }
210
211        /**
212         * Add the given, single header value under the given name.
213         * @param headerName  the header name
214         * @param headerValue the header value
215         * @throws UnsupportedOperationException if adding headers is not supported
216         * @see #put(String, List)
217         * @see #set(String, String)
218         */
219        @Override
220        public void add(String headerName, String headerValue) {
221                this.headers.add(headerName, headerValue);
222        }
223
224        /**
225         * Set the given, single header value under the given name.
226         * @param headerName  the header name
227         * @param headerValue the header value
228         * @throws UnsupportedOperationException if adding headers is not supported
229         * @see #put(String, List)
230         * @see #add(String, String)
231         */
232        @Override
233        public void set(String headerName, String headerValue) {
234                this.headers.set(headerName, headerValue);
235        }
236
237        @Override
238        public void setAll(Map<String, String> values) {
239                this.headers.setAll(values);
240        }
241
242        @Override
243        public Map<String, String> toSingleValueMap() {
244                return this.headers.toSingleValueMap();
245        }
246
247        // Map implementation
248
249        @Override
250        public int size() {
251                return this.headers.size();
252        }
253
254        @Override
255        public boolean isEmpty() {
256                return this.headers.isEmpty();
257        }
258
259        @Override
260        public boolean containsKey(Object key) {
261                return this.headers.containsKey(key);
262        }
263
264        @Override
265        public boolean containsValue(Object value) {
266                return this.headers.containsValue(value);
267        }
268
269        @Override
270        public List<String> get(Object key) {
271                return this.headers.get(key);
272        }
273
274        @Override
275        public List<String> put(String key, List<String> value) {
276                return this.headers.put(key, value);
277        }
278
279        @Override
280        public List<String> remove(Object key) {
281                return this.headers.remove(key);
282        }
283
284        @Override
285        public void putAll(Map<? extends String, ? extends List<String>> m) {
286                this.headers.putAll(m);
287        }
288
289        @Override
290        public void clear() {
291                this.headers.clear();
292        }
293
294        @Override
295        public Set<String> keySet() {
296                return this.headers.keySet();
297        }
298
299        @Override
300        public Collection<List<String>> values() {
301                return this.headers.values();
302        }
303
304        @Override
305        public Set<Entry<String, List<String>>> entrySet() {
306                return this.headers.entrySet();
307        }
308
309
310        @Override
311        public boolean equals(Object other) {
312                if (this == other) {
313                        return true;
314                }
315                if (!(other instanceof WebSocketHttpHeaders)) {
316                        return false;
317                }
318                WebSocketHttpHeaders otherHeaders = (WebSocketHttpHeaders) other;
319                return this.headers.equals(otherHeaders.headers);
320        }
321
322        @Override
323        public int hashCode() {
324                return this.headers.hashCode();
325        }
326
327        @Override
328        public String toString() {
329                return this.headers.toString();
330        }
331
332}