001/* 002 * Copyright 2002-2017 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.adapter.jetty; 018 019import java.io.IOException; 020import java.lang.reflect.Method; 021import java.net.InetSocketAddress; 022import java.net.URI; 023import java.security.Principal; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.List; 027import java.util.Map; 028 029import org.eclipse.jetty.websocket.api.RemoteEndpoint; 030import org.eclipse.jetty.websocket.api.Session; 031import org.eclipse.jetty.websocket.api.UpgradeRequest; 032import org.eclipse.jetty.websocket.api.UpgradeResponse; 033import org.eclipse.jetty.websocket.api.WebSocketException; 034import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; 035 036import org.springframework.http.HttpHeaders; 037import org.springframework.util.CollectionUtils; 038import org.springframework.util.ObjectUtils; 039import org.springframework.util.ReflectionUtils; 040import org.springframework.web.socket.BinaryMessage; 041import org.springframework.web.socket.CloseStatus; 042import org.springframework.web.socket.PingMessage; 043import org.springframework.web.socket.PongMessage; 044import org.springframework.web.socket.TextMessage; 045import org.springframework.web.socket.WebSocketExtension; 046import org.springframework.web.socket.WebSocketSession; 047import org.springframework.web.socket.adapter.AbstractWebSocketSession; 048 049/** 050 * A {@link WebSocketSession} for use with the Jetty 9.3/9.4 WebSocket API. 051 * 052 * @author Phillip Webb 053 * @author Rossen Stoyanchev 054 * @author Brian Clozel 055 * @author Juergen Hoeller 056 * @since 4.0 057 */ 058public class JettyWebSocketSession extends AbstractWebSocketSession<Session> { 059 060 // As of Jetty 9.4, UpgradeRequest and UpgradeResponse are interfaces instead of classes 061 private static final boolean directInterfaceCalls; 062 063 private static Method getUpgradeRequest; 064 private static Method getUpgradeResponse; 065 private static Method getRequestURI; 066 private static Method getHeaders; 067 private static Method getUserPrincipal; 068 private static Method getAcceptedSubProtocol; 069 private static Method getExtensions; 070 071 static { 072 directInterfaceCalls = UpgradeRequest.class.isInterface(); 073 if (!directInterfaceCalls) { 074 try { 075 getUpgradeRequest = Session.class.getMethod("getUpgradeRequest"); 076 getUpgradeResponse = Session.class.getMethod("getUpgradeResponse"); 077 getRequestURI = UpgradeRequest.class.getMethod("getRequestURI"); 078 getHeaders = UpgradeRequest.class.getMethod("getHeaders"); 079 getUserPrincipal = UpgradeRequest.class.getMethod("getUserPrincipal"); 080 getAcceptedSubProtocol = UpgradeResponse.class.getMethod("getAcceptedSubProtocol"); 081 getExtensions = UpgradeResponse.class.getMethod("getExtensions"); 082 } 083 catch (NoSuchMethodException ex) { 084 throw new IllegalStateException("Incompatible Jetty API", ex); 085 } 086 } 087 } 088 089 090 private String id; 091 092 private URI uri; 093 094 private HttpHeaders headers; 095 096 private String acceptedProtocol; 097 098 private List<WebSocketExtension> extensions; 099 100 private Principal user; 101 102 103 /** 104 * Create a new {@link JettyWebSocketSession} instance. 105 * @param attributes attributes from the HTTP handshake to associate with the WebSocket session 106 */ 107 public JettyWebSocketSession(Map<String, Object> attributes) { 108 this(attributes, null); 109 } 110 111 /** 112 * Create a new {@link JettyWebSocketSession} instance associated with the given user. 113 * @param attributes attributes from the HTTP handshake to associate with the WebSocket 114 * session; the provided attributes are copied, the original map is not used. 115 * @param user the user associated with the session; if {@code null} we'll fallback on the 116 * user available via {@link org.eclipse.jetty.websocket.api.Session#getUpgradeRequest()} 117 */ 118 public JettyWebSocketSession(Map<String, Object> attributes, Principal user) { 119 super(attributes); 120 this.user = user; 121 } 122 123 124 @Override 125 public String getId() { 126 checkNativeSessionInitialized(); 127 return this.id; 128 } 129 130 @Override 131 public URI getUri() { 132 checkNativeSessionInitialized(); 133 return this.uri; 134 } 135 136 @Override 137 public HttpHeaders getHandshakeHeaders() { 138 checkNativeSessionInitialized(); 139 return this.headers; 140 } 141 142 @Override 143 public String getAcceptedProtocol() { 144 checkNativeSessionInitialized(); 145 return this.acceptedProtocol; 146 } 147 148 @Override 149 public List<WebSocketExtension> getExtensions() { 150 checkNativeSessionInitialized(); 151 return this.extensions; 152 } 153 154 @Override 155 public Principal getPrincipal() { 156 return this.user; 157 } 158 159 @Override 160 public InetSocketAddress getLocalAddress() { 161 checkNativeSessionInitialized(); 162 return getNativeSession().getLocalAddress(); 163 } 164 165 @Override 166 public InetSocketAddress getRemoteAddress() { 167 checkNativeSessionInitialized(); 168 return getNativeSession().getRemoteAddress(); 169 } 170 171 @Override 172 public void setTextMessageSizeLimit(int messageSizeLimit) { 173 checkNativeSessionInitialized(); 174 getNativeSession().getPolicy().setMaxTextMessageSize(messageSizeLimit); 175 } 176 177 @Override 178 public int getTextMessageSizeLimit() { 179 checkNativeSessionInitialized(); 180 return getNativeSession().getPolicy().getMaxTextMessageSize(); 181 } 182 183 @Override 184 public void setBinaryMessageSizeLimit(int messageSizeLimit) { 185 checkNativeSessionInitialized(); 186 getNativeSession().getPolicy().setMaxBinaryMessageSize(messageSizeLimit); 187 } 188 189 @Override 190 public int getBinaryMessageSizeLimit() { 191 checkNativeSessionInitialized(); 192 return getNativeSession().getPolicy().getMaxBinaryMessageSize(); 193 } 194 195 @Override 196 public boolean isOpen() { 197 return (getNativeSession() != null && getNativeSession().isOpen()); 198 } 199 200 201 @Override 202 public void initializeNativeSession(Session session) { 203 super.initializeNativeSession(session); 204 if (directInterfaceCalls) { 205 initializeJettySessionDirectly(session); 206 } 207 else { 208 initializeJettySessionReflectively(session); 209 } 210 } 211 212 private void initializeJettySessionDirectly(Session session) { 213 this.id = ObjectUtils.getIdentityHexString(getNativeSession()); 214 this.uri = session.getUpgradeRequest().getRequestURI(); 215 216 HttpHeaders headers = new HttpHeaders(); 217 headers.putAll(session.getUpgradeRequest().getHeaders()); 218 this.headers = HttpHeaders.readOnlyHttpHeaders(headers); 219 220 this.acceptedProtocol = session.getUpgradeResponse().getAcceptedSubProtocol(); 221 222 List<ExtensionConfig> jettyExtensions = session.getUpgradeResponse().getExtensions(); 223 if (!CollectionUtils.isEmpty(jettyExtensions)) { 224 List<WebSocketExtension> extensions = new ArrayList<WebSocketExtension>(jettyExtensions.size()); 225 for (ExtensionConfig jettyExtension : jettyExtensions) { 226 extensions.add(new WebSocketExtension(jettyExtension.getName(), jettyExtension.getParameters())); 227 } 228 this.extensions = Collections.unmodifiableList(extensions); 229 } 230 else { 231 this.extensions = Collections.emptyList(); 232 } 233 234 if (this.user == null) { 235 this.user = session.getUpgradeRequest().getUserPrincipal(); 236 } 237 } 238 239 @SuppressWarnings("unchecked") 240 private void initializeJettySessionReflectively(Session session) { 241 Object request = ReflectionUtils.invokeMethod(getUpgradeRequest, session); 242 Object response = ReflectionUtils.invokeMethod(getUpgradeResponse, session); 243 244 this.id = ObjectUtils.getIdentityHexString(getNativeSession()); 245 this.uri = (URI) ReflectionUtils.invokeMethod(getRequestURI, request); 246 247 HttpHeaders headers = new HttpHeaders(); 248 headers.putAll((Map<String, List<String>>) ReflectionUtils.invokeMethod(getHeaders, request)); 249 this.headers = HttpHeaders.readOnlyHttpHeaders(headers); 250 251 this.acceptedProtocol = (String) ReflectionUtils.invokeMethod(getAcceptedSubProtocol, response); 252 253 List<ExtensionConfig> jettyExtensions = (List<ExtensionConfig>) ReflectionUtils.invokeMethod(getExtensions, response); 254 if (!CollectionUtils.isEmpty(jettyExtensions)) { 255 List<WebSocketExtension> extensions = new ArrayList<WebSocketExtension>(jettyExtensions.size()); 256 for (ExtensionConfig jettyExtension : jettyExtensions) { 257 extensions.add(new WebSocketExtension(jettyExtension.getName(), jettyExtension.getParameters())); 258 } 259 this.extensions = Collections.unmodifiableList(extensions); 260 } 261 else { 262 this.extensions = Collections.emptyList(); 263 } 264 265 if (this.user == null) { 266 this.user = (Principal) ReflectionUtils.invokeMethod(getUserPrincipal, request); 267 } 268 } 269 270 271 @Override 272 protected void sendTextMessage(TextMessage message) throws IOException { 273 getRemoteEndpoint().sendString(message.getPayload()); 274 } 275 276 @Override 277 protected void sendBinaryMessage(BinaryMessage message) throws IOException { 278 getRemoteEndpoint().sendBytes(message.getPayload()); 279 } 280 281 @Override 282 protected void sendPingMessage(PingMessage message) throws IOException { 283 getRemoteEndpoint().sendPing(message.getPayload()); 284 } 285 286 @Override 287 protected void sendPongMessage(PongMessage message) throws IOException { 288 getRemoteEndpoint().sendPong(message.getPayload()); 289 } 290 291 private RemoteEndpoint getRemoteEndpoint() throws IOException { 292 try { 293 return getNativeSession().getRemote(); 294 } 295 catch (WebSocketException ex) { 296 throw new IOException("Unable to obtain RemoteEndpoint in session " + getId(), ex); 297 } 298 } 299 300 @Override 301 protected void closeInternal(CloseStatus status) throws IOException { 302 getNativeSession().close(status.getCode(), status.getReason()); 303 } 304 305}