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}