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