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}