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}