001/* 002 * Copyright 2002-2018 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.net.InetSocketAddress; 021import java.net.URI; 022import java.security.Principal; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.List; 026import java.util.Map; 027 028import org.eclipse.jetty.websocket.api.RemoteEndpoint; 029import org.eclipse.jetty.websocket.api.Session; 030import org.eclipse.jetty.websocket.api.WebSocketException; 031import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; 032 033import org.springframework.http.HttpHeaders; 034import org.springframework.lang.Nullable; 035import org.springframework.util.Assert; 036import org.springframework.util.CollectionUtils; 037import org.springframework.web.socket.BinaryMessage; 038import org.springframework.web.socket.CloseStatus; 039import org.springframework.web.socket.PingMessage; 040import org.springframework.web.socket.PongMessage; 041import org.springframework.web.socket.TextMessage; 042import org.springframework.web.socket.WebSocketExtension; 043import org.springframework.web.socket.WebSocketSession; 044import org.springframework.web.socket.adapter.AbstractWebSocketSession; 045 046/** 047 * A {@link WebSocketSession} for use with the Jetty 9.4 WebSocket API. 048 * 049 * @author Phillip Webb 050 * @author Rossen Stoyanchev 051 * @author Brian Clozel 052 * @author Juergen Hoeller 053 * @since 4.0 054 */ 055public class JettyWebSocketSession extends AbstractWebSocketSession<Session> { 056 057 private final String id; 058 059 @Nullable 060 private URI uri; 061 062 @Nullable 063 private HttpHeaders headers; 064 065 @Nullable 066 private String acceptedProtocol; 067 068 @Nullable 069 private List<WebSocketExtension> extensions; 070 071 @Nullable 072 private Principal user; 073 074 075 /** 076 * Create a new {@link JettyWebSocketSession} instance. 077 * @param attributes the attributes from the HTTP handshake to associate with the WebSocket session 078 */ 079 public JettyWebSocketSession(Map<String, Object> attributes) { 080 this(attributes, null); 081 } 082 083 /** 084 * Create a new {@link JettyWebSocketSession} instance associated with the given user. 085 * @param attributes the attributes from the HTTP handshake to associate with the WebSocket 086 * session; the provided attributes are copied, the original map is not used. 087 * @param user the user associated with the session; if {@code null} we'll fallback on the 088 * user available via {@link org.eclipse.jetty.websocket.api.Session#getUpgradeRequest()} 089 */ 090 public JettyWebSocketSession(Map<String, Object> attributes, @Nullable Principal user) { 091 super(attributes); 092 this.id = idGenerator.generateId().toString(); 093 this.user = user; 094 } 095 096 097 @Override 098 public String getId() { 099 return this.id; 100 } 101 102 @Override 103 @Nullable 104 public URI getUri() { 105 checkNativeSessionInitialized(); 106 return this.uri; 107 } 108 109 @Override 110 public HttpHeaders getHandshakeHeaders() { 111 Assert.state(this.headers != null, "WebSocket session is not yet initialized"); 112 return this.headers; 113 } 114 115 @Override 116 public String getAcceptedProtocol() { 117 checkNativeSessionInitialized(); 118 return this.acceptedProtocol; 119 } 120 121 @Override 122 public List<WebSocketExtension> getExtensions() { 123 Assert.state(this.extensions != null, "WebSocket session is not yet initialized"); 124 return this.extensions; 125 } 126 127 @Override 128 public Principal getPrincipal() { 129 return this.user; 130 } 131 132 @Override 133 public InetSocketAddress getLocalAddress() { 134 checkNativeSessionInitialized(); 135 return getNativeSession().getLocalAddress(); 136 } 137 138 @Override 139 public InetSocketAddress getRemoteAddress() { 140 checkNativeSessionInitialized(); 141 return getNativeSession().getRemoteAddress(); 142 } 143 144 @Override 145 public void setTextMessageSizeLimit(int messageSizeLimit) { 146 checkNativeSessionInitialized(); 147 getNativeSession().getPolicy().setMaxTextMessageSize(messageSizeLimit); 148 } 149 150 @Override 151 public int getTextMessageSizeLimit() { 152 checkNativeSessionInitialized(); 153 return getNativeSession().getPolicy().getMaxTextMessageSize(); 154 } 155 156 @Override 157 public void setBinaryMessageSizeLimit(int messageSizeLimit) { 158 checkNativeSessionInitialized(); 159 getNativeSession().getPolicy().setMaxBinaryMessageSize(messageSizeLimit); 160 } 161 162 @Override 163 public int getBinaryMessageSizeLimit() { 164 checkNativeSessionInitialized(); 165 return getNativeSession().getPolicy().getMaxBinaryMessageSize(); 166 } 167 168 @Override 169 public boolean isOpen() { 170 return getNativeSession().isOpen(); 171 } 172 173 174 @Override 175 public void initializeNativeSession(Session session) { 176 super.initializeNativeSession(session); 177 178 this.uri = session.getUpgradeRequest().getRequestURI(); 179 180 HttpHeaders headers = new HttpHeaders(); 181 headers.putAll(session.getUpgradeRequest().getHeaders()); 182 this.headers = HttpHeaders.readOnlyHttpHeaders(headers); 183 184 this.acceptedProtocol = session.getUpgradeResponse().getAcceptedSubProtocol(); 185 186 List<ExtensionConfig> jettyExtensions = session.getUpgradeResponse().getExtensions(); 187 if (!CollectionUtils.isEmpty(jettyExtensions)) { 188 List<WebSocketExtension> extensions = new ArrayList<>(jettyExtensions.size()); 189 for (ExtensionConfig jettyExtension : jettyExtensions) { 190 extensions.add(new WebSocketExtension(jettyExtension.getName(), jettyExtension.getParameters())); 191 } 192 this.extensions = Collections.unmodifiableList(extensions); 193 } 194 else { 195 this.extensions = Collections.emptyList(); 196 } 197 198 if (this.user == null) { 199 this.user = session.getUpgradeRequest().getUserPrincipal(); 200 } 201 } 202 203 204 @Override 205 protected void sendTextMessage(TextMessage message) throws IOException { 206 getRemoteEndpoint().sendString(message.getPayload()); 207 } 208 209 @Override 210 protected void sendBinaryMessage(BinaryMessage message) throws IOException { 211 getRemoteEndpoint().sendBytes(message.getPayload()); 212 } 213 214 @Override 215 protected void sendPingMessage(PingMessage message) throws IOException { 216 getRemoteEndpoint().sendPing(message.getPayload()); 217 } 218 219 @Override 220 protected void sendPongMessage(PongMessage message) throws IOException { 221 getRemoteEndpoint().sendPong(message.getPayload()); 222 } 223 224 private RemoteEndpoint getRemoteEndpoint() throws IOException { 225 try { 226 return getNativeSession().getRemote(); 227 } 228 catch (WebSocketException ex) { 229 throw new IOException("Unable to obtain RemoteEndpoint in session " + getId(), ex); 230 } 231 } 232 233 @Override 234 protected void closeInternal(CloseStatus status) throws IOException { 235 getNativeSession().close(status.getCode(), status.getReason()); 236 } 237 238}