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.client.standard; 018 019import java.util.Arrays; 020import java.util.List; 021 022import javax.websocket.ClientEndpointConfig; 023import javax.websocket.ClientEndpointConfig.Configurator; 024import javax.websocket.ContainerProvider; 025import javax.websocket.Decoder; 026import javax.websocket.Encoder; 027import javax.websocket.Endpoint; 028import javax.websocket.Extension; 029import javax.websocket.Session; 030import javax.websocket.WebSocketContainer; 031 032import org.springframework.beans.factory.BeanFactory; 033import org.springframework.beans.factory.BeanFactoryAware; 034import org.springframework.core.task.SimpleAsyncTaskExecutor; 035import org.springframework.core.task.TaskExecutor; 036import org.springframework.lang.Nullable; 037import org.springframework.util.Assert; 038import org.springframework.web.socket.client.ConnectionManagerSupport; 039import org.springframework.web.socket.handler.BeanCreatingHandlerProvider; 040 041/** 042 * A WebSocket connection manager that is given a URI, an {@link Endpoint}, connects to a 043 * WebSocket server through the {@link #start()} and {@link #stop()} methods. If 044 * {@link #setAutoStartup(boolean)} is set to {@code true} this will be done automatically 045 * when the Spring ApplicationContext is refreshed. 046 * 047 * @author Rossen Stoyanchev 048 * @since 4.0 049 * @see AnnotatedEndpointConnectionManager 050 */ 051public class EndpointConnectionManager extends ConnectionManagerSupport implements BeanFactoryAware { 052 053 @Nullable 054 private final Endpoint endpoint; 055 056 @Nullable 057 private final BeanCreatingHandlerProvider<Endpoint> endpointProvider; 058 059 private final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create(); 060 061 private WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer(); 062 063 private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("EndpointConnectionManager-"); 064 065 @Nullable 066 private volatile Session session; 067 068 069 public EndpointConnectionManager(Endpoint endpoint, String uriTemplate, Object... uriVariables) { 070 super(uriTemplate, uriVariables); 071 Assert.notNull(endpoint, "endpoint must not be null"); 072 this.endpoint = endpoint; 073 this.endpointProvider = null; 074 } 075 076 public EndpointConnectionManager(Class<? extends Endpoint> endpointClass, String uriTemplate, Object... uriVars) { 077 super(uriTemplate, uriVars); 078 Assert.notNull(endpointClass, "endpointClass must not be null"); 079 this.endpoint = null; 080 this.endpointProvider = new BeanCreatingHandlerProvider<>(endpointClass); 081 } 082 083 084 public void setSupportedProtocols(String... protocols) { 085 this.configBuilder.preferredSubprotocols(Arrays.asList(protocols)); 086 } 087 088 public void setExtensions(Extension... extensions) { 089 this.configBuilder.extensions(Arrays.asList(extensions)); 090 } 091 092 public void setEncoders(List<Class<? extends Encoder>> encoders) { 093 this.configBuilder.encoders(encoders); 094 } 095 096 public void setDecoders(List<Class<? extends Decoder>> decoders) { 097 this.configBuilder.decoders(decoders); 098 } 099 100 public void setConfigurator(Configurator configurator) { 101 this.configBuilder.configurator(configurator); 102 } 103 104 public void setWebSocketContainer(WebSocketContainer webSocketContainer) { 105 this.webSocketContainer = webSocketContainer; 106 } 107 108 public WebSocketContainer getWebSocketContainer() { 109 return this.webSocketContainer; 110 } 111 112 @Override 113 public void setBeanFactory(BeanFactory beanFactory) { 114 if (this.endpointProvider != null) { 115 this.endpointProvider.setBeanFactory(beanFactory); 116 } 117 } 118 119 /** 120 * Set a {@link TaskExecutor} to use to open connections. 121 * By default {@link SimpleAsyncTaskExecutor} is used. 122 */ 123 public void setTaskExecutor(TaskExecutor taskExecutor) { 124 Assert.notNull(taskExecutor, "TaskExecutor must not be null"); 125 this.taskExecutor = taskExecutor; 126 } 127 128 /** 129 * Return the configured {@link TaskExecutor}. 130 */ 131 public TaskExecutor getTaskExecutor() { 132 return this.taskExecutor; 133 } 134 135 136 @Override 137 protected void openConnection() { 138 this.taskExecutor.execute(() -> { 139 try { 140 if (logger.isInfoEnabled()) { 141 logger.info("Connecting to WebSocket at " + getUri()); 142 } 143 Endpoint endpointToUse = this.endpoint; 144 if (endpointToUse == null) { 145 Assert.state(this.endpointProvider != null, "No endpoint set"); 146 endpointToUse = this.endpointProvider.getHandler(); 147 } 148 ClientEndpointConfig endpointConfig = this.configBuilder.build(); 149 this.session = getWebSocketContainer().connectToServer(endpointToUse, endpointConfig, getUri()); 150 logger.info("Successfully connected to WebSocket"); 151 } 152 catch (Throwable ex) { 153 logger.error("Failed to connect to WebSocket", ex); 154 } 155 }); 156 } 157 158 @Override 159 protected void closeConnection() throws Exception { 160 try { 161 Session session = this.session; 162 if (session != null && session.isOpen()) { 163 session.close(); 164 } 165 } 166 finally { 167 this.session = null; 168 } 169 } 170 171 @Override 172 protected boolean isConnected() { 173 Session session = this.session; 174 return (session != null && session.isOpen()); 175 } 176 177}