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.standard; 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 javax.websocket.CloseReason; 029import javax.websocket.CloseReason.CloseCodes; 030import javax.websocket.Extension; 031import javax.websocket.Session; 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 standard WebSocket for Java API. 048 * 049 * @author Rossen Stoyanchev 050 * @since 4.0 051 */ 052public class StandardWebSocketSession extends AbstractWebSocketSession<Session> { 053 054 private final String id; 055 056 @Nullable 057 private URI uri; 058 059 private final HttpHeaders handshakeHeaders; 060 061 @Nullable 062 private String acceptedProtocol; 063 064 @Nullable 065 private List<WebSocketExtension> extensions; 066 067 @Nullable 068 private Principal user; 069 070 @Nullable 071 private final InetSocketAddress localAddress; 072 073 @Nullable 074 private final InetSocketAddress remoteAddress; 075 076 077 /** 078 * Constructor for a standard WebSocket session. 079 * @param headers the headers of the handshake request 080 * @param attributes the attributes from the HTTP handshake to associate with the WebSocket 081 * session; the provided attributes are copied, the original map is not used. 082 * @param localAddress the address on which the request was received 083 * @param remoteAddress the address of the remote client 084 */ 085 public StandardWebSocketSession(@Nullable HttpHeaders headers, @Nullable Map<String, Object> attributes, 086 @Nullable InetSocketAddress localAddress, @Nullable InetSocketAddress remoteAddress) { 087 088 this(headers, attributes, localAddress, remoteAddress, null); 089 } 090 091 /** 092 * Constructor that associates a user with the WebSocket session. 093 * @param headers the headers of the handshake request 094 * @param attributes the attributes from the HTTP handshake to associate with the WebSocket session 095 * @param localAddress the address on which the request was received 096 * @param remoteAddress the address of the remote client 097 * @param user the user associated with the session; if {@code null} we'll 098 * fallback on the user available in the underlying WebSocket session 099 */ 100 public StandardWebSocketSession(@Nullable HttpHeaders headers, @Nullable Map<String, Object> attributes, 101 @Nullable InetSocketAddress localAddress, @Nullable InetSocketAddress remoteAddress, 102 @Nullable Principal user) { 103 104 super(attributes); 105 this.id = idGenerator.generateId().toString(); 106 headers = (headers != null ? headers : new HttpHeaders()); 107 this.handshakeHeaders = HttpHeaders.readOnlyHttpHeaders(headers); 108 this.user = user; 109 this.localAddress = localAddress; 110 this.remoteAddress = remoteAddress; 111 } 112 113 114 @Override 115 public String getId() { 116 return this.id; 117 } 118 119 @Override 120 @Nullable 121 public URI getUri() { 122 checkNativeSessionInitialized(); 123 return this.uri; 124 } 125 126 @Override 127 public HttpHeaders getHandshakeHeaders() { 128 return this.handshakeHeaders; 129 } 130 131 @Override 132 public String getAcceptedProtocol() { 133 checkNativeSessionInitialized(); 134 return this.acceptedProtocol; 135 } 136 137 @Override 138 public List<WebSocketExtension> getExtensions() { 139 Assert.state(this.extensions != null, "WebSocket session is not yet initialized"); 140 return this.extensions; 141 } 142 143 @Override 144 public Principal getPrincipal() { 145 return this.user; 146 } 147 148 @Override 149 @Nullable 150 public InetSocketAddress getLocalAddress() { 151 return this.localAddress; 152 } 153 154 @Override 155 @Nullable 156 public InetSocketAddress getRemoteAddress() { 157 return this.remoteAddress; 158 } 159 160 @Override 161 public void setTextMessageSizeLimit(int messageSizeLimit) { 162 checkNativeSessionInitialized(); 163 getNativeSession().setMaxTextMessageBufferSize(messageSizeLimit); 164 } 165 166 @Override 167 public int getTextMessageSizeLimit() { 168 checkNativeSessionInitialized(); 169 return getNativeSession().getMaxTextMessageBufferSize(); 170 } 171 172 @Override 173 public void setBinaryMessageSizeLimit(int messageSizeLimit) { 174 checkNativeSessionInitialized(); 175 getNativeSession().setMaxBinaryMessageBufferSize(messageSizeLimit); 176 } 177 178 @Override 179 public int getBinaryMessageSizeLimit() { 180 checkNativeSessionInitialized(); 181 return getNativeSession().getMaxBinaryMessageBufferSize(); 182 } 183 184 @Override 185 public boolean isOpen() { 186 return getNativeSession().isOpen(); 187 } 188 189 @Override 190 public void initializeNativeSession(Session session) { 191 super.initializeNativeSession(session); 192 193 this.uri = session.getRequestURI(); 194 this.acceptedProtocol = session.getNegotiatedSubprotocol(); 195 196 List<Extension> standardExtensions = getNativeSession().getNegotiatedExtensions(); 197 if (!CollectionUtils.isEmpty(standardExtensions)) { 198 this.extensions = new ArrayList<>(standardExtensions.size()); 199 for (Extension standardExtension : standardExtensions) { 200 this.extensions.add(new StandardToWebSocketExtensionAdapter(standardExtension)); 201 } 202 this.extensions = Collections.unmodifiableList(this.extensions); 203 } 204 else { 205 this.extensions = Collections.emptyList(); 206 } 207 208 if (this.user == null) { 209 this.user = session.getUserPrincipal(); 210 } 211 } 212 213 @Override 214 protected void sendTextMessage(TextMessage message) throws IOException { 215 getNativeSession().getBasicRemote().sendText(message.getPayload(), message.isLast()); 216 } 217 218 @Override 219 protected void sendBinaryMessage(BinaryMessage message) throws IOException { 220 getNativeSession().getBasicRemote().sendBinary(message.getPayload(), message.isLast()); 221 } 222 223 @Override 224 protected void sendPingMessage(PingMessage message) throws IOException { 225 getNativeSession().getBasicRemote().sendPing(message.getPayload()); 226 } 227 228 @Override 229 protected void sendPongMessage(PongMessage message) throws IOException { 230 getNativeSession().getBasicRemote().sendPong(message.getPayload()); 231 } 232 233 @Override 234 protected void closeInternal(CloseStatus status) throws IOException { 235 getNativeSession().close(new CloseReason(CloseCodes.getCloseCode(status.getCode()), status.getReason())); 236 } 237 238}