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}