1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.jsr356;
20
21 import java.io.IOException;
22 import java.net.URI;
23 import java.util.HashSet;
24 import java.util.Map;
25 import java.util.Objects;
26 import java.util.Set;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ExecutionException;
29 import java.util.concurrent.Executor;
30 import java.util.concurrent.Future;
31
32 import javax.websocket.ClientEndpoint;
33 import javax.websocket.ClientEndpointConfig;
34 import javax.websocket.DeploymentException;
35 import javax.websocket.Endpoint;
36 import javax.websocket.Extension;
37 import javax.websocket.Session;
38 import javax.websocket.WebSocketContainer;
39
40 import org.eclipse.jetty.util.component.ContainerLifeCycle;
41 import org.eclipse.jetty.util.thread.ShutdownThread;
42 import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
43 import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
44 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
45 import org.eclipse.jetty.websocket.client.WebSocketClient;
46 import org.eclipse.jetty.websocket.client.io.UpgradeListener;
47 import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
48 import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
49 import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
50 import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
51 import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
52 import org.eclipse.jetty.websocket.jsr356.encoders.PrimitiveEncoderMetadataSet;
53 import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
54 import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEventDriverFactory;
55 import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
56
57
58
59
60
61
62 public class ClientContainer extends ContainerLifeCycle implements WebSocketContainer
63 {
64
65 private final DecoderFactory decoderFactory;
66
67 private final EncoderFactory encoderFactory;
68
69
70 private final Map<Class<?>, EndpointMetadata> endpointClientMetadataCache;
71
72 private WebSocketClient client;
73
74 public ClientContainer()
75 {
76 this(null);
77 }
78
79 public ClientContainer(Executor executor)
80 {
81 endpointClientMetadataCache = new ConcurrentHashMap<>();
82 decoderFactory = new DecoderFactory(PrimitiveDecoderMetadataSet.INSTANCE);
83 encoderFactory = new EncoderFactory(PrimitiveEncoderMetadataSet.INSTANCE);
84
85 EmptyClientEndpointConfig empty = new EmptyClientEndpointConfig();
86 decoderFactory.init(empty);
87 encoderFactory.init(empty);
88
89 client = new WebSocketClient(executor);
90 client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
91 client.setSessionFactory(new JsrSessionFactory(this));
92 addBean(client);
93 }
94
95 private Session connect(EndpointInstance instance, URI path) throws IOException
96 {
97 Objects.requireNonNull(instance,"EndpointInstance cannot be null");
98 Objects.requireNonNull(path,"Path cannot be null");
99
100 ClientEndpointConfig config = (ClientEndpointConfig)instance.getConfig();
101 ClientUpgradeRequest req = new ClientUpgradeRequest();
102 UpgradeListener upgradeListener = null;
103
104 for (Extension ext : config.getExtensions())
105 {
106 req.addExtensions(new JsrExtensionConfig(ext));
107 }
108
109 if (config.getPreferredSubprotocols().size() > 0)
110 {
111 req.setSubProtocols(config.getPreferredSubprotocols());
112 }
113
114 if (config.getConfigurator() != null)
115 {
116 upgradeListener = new JsrUpgradeListener(config.getConfigurator());
117 }
118
119 Future<org.eclipse.jetty.websocket.api.Session> futSess = client.connect(instance,path,req,upgradeListener);
120 try
121 {
122 return (JsrSession)futSess.get();
123 }
124 catch (InterruptedException e)
125 {
126 throw new IOException("Connect failure",e);
127 }
128 catch (ExecutionException e)
129 {
130
131 Throwable cause = e.getCause();
132
133 if (cause instanceof IOException)
134 {
135
136 throw (IOException)cause;
137 }
138 else
139 {
140 throw new IOException("Connect failure",cause);
141 }
142 }
143 }
144
145 @Override
146 public Session connectToServer(Class<? extends Endpoint> endpointClass, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
147 {
148 EndpointInstance instance = newClientEndpointInstance(endpointClass,config);
149 return connect(instance,path);
150 }
151
152 @Override
153 public Session connectToServer(Class<?> annotatedEndpointClass, URI path) throws DeploymentException, IOException
154 {
155 EndpointInstance instance = newClientEndpointInstance(annotatedEndpointClass,null);
156 return connect(instance,path);
157 }
158
159 @Override
160 public Session connectToServer(Endpoint endpoint, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
161 {
162 EndpointInstance instance = newClientEndpointInstance(endpoint,config);
163 return connect(instance,path);
164 }
165
166 @Override
167 public Session connectToServer(Object endpoint, URI path) throws DeploymentException, IOException
168 {
169 EndpointInstance instance = newClientEndpointInstance(endpoint,null);
170 return connect(instance,path);
171 }
172
173 @Override
174 protected void doStart() throws Exception
175 {
176 super.doStart();
177 ShutdownThread.register(client);
178 }
179
180 @Override
181 protected void doStop() throws Exception
182 {
183 endpointClientMetadataCache.clear();
184 ShutdownThread.deregister(client);
185 super.doStop();
186 }
187
188 public WebSocketClient getClient()
189 {
190 return client;
191 }
192
193 public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint)
194 {
195 EndpointMetadata metadata = null;
196
197 synchronized (endpointClientMetadataCache)
198 {
199 metadata = endpointClientMetadataCache.get(endpoint);
200
201 if (metadata != null)
202 {
203 return metadata;
204 }
205
206 ClientEndpoint anno = endpoint.getAnnotation(ClientEndpoint.class);
207 if (anno != null)
208 {
209
210 AnnotatedClientEndpointMetadata annoMetadata = new AnnotatedClientEndpointMetadata(this,endpoint);
211 AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(annoMetadata);
212 scanner.scan();
213 metadata = annoMetadata;
214 }
215 else if (Endpoint.class.isAssignableFrom(endpoint))
216 {
217
218 @SuppressWarnings("unchecked")
219 Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
220 metadata = new SimpleEndpointMetadata(eendpoint);
221 }
222 else
223 {
224 StringBuilder err = new StringBuilder();
225 err.append("Not a recognized websocket [");
226 err.append(endpoint.getName());
227 err.append("] does not extend @").append(ClientEndpoint.class.getName());
228 err.append(" or extend from ").append(Endpoint.class.getName());
229 throw new InvalidWebSocketException("Unable to identify as valid Endpoint: " + endpoint);
230 }
231
232 endpointClientMetadataCache.put(endpoint,metadata);
233 return metadata;
234 }
235 }
236
237 public DecoderFactory getDecoderFactory()
238 {
239 return decoderFactory;
240 }
241
242 @Override
243 public long getDefaultAsyncSendTimeout()
244 {
245 return client.getAsyncWriteTimeout();
246 }
247
248 @Override
249 public int getDefaultMaxBinaryMessageBufferSize()
250 {
251 return client.getMaxBinaryMessageBufferSize();
252 }
253
254 @Override
255 public long getDefaultMaxSessionIdleTimeout()
256 {
257 return client.getMaxIdleTimeout();
258 }
259
260 @Override
261 public int getDefaultMaxTextMessageBufferSize()
262 {
263 return client.getMaxTextMessageBufferSize();
264 }
265
266 public EncoderFactory getEncoderFactory()
267 {
268 return encoderFactory;
269 }
270
271 @Override
272 public Set<Extension> getInstalledExtensions()
273 {
274 Set<Extension> ret = new HashSet<>();
275 ExtensionFactory extensions = client.getExtensionFactory();
276
277 for (String name : extensions.getExtensionNames())
278 {
279 ret.add(new JsrExtension(name));
280 }
281
282 return ret;
283 }
284
285
286
287
288 public Set<Session> getOpenSessions()
289 {
290
291 return null;
292 }
293
294 private EndpointInstance newClientEndpointInstance(Class<?> endpointClass, ClientEndpointConfig config)
295 {
296 try
297 {
298 return newClientEndpointInstance(endpointClass.newInstance(),config);
299 }
300 catch (InstantiationException | IllegalAccessException e)
301 {
302 throw new InvalidWebSocketException("Unable to instantiate websocket: " + endpointClass.getClass());
303 }
304 }
305
306 public EndpointInstance newClientEndpointInstance(Object endpoint, ClientEndpointConfig config)
307 {
308 EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass());
309 ClientEndpointConfig cec = config;
310 if (config == null)
311 {
312 if (metadata instanceof AnnotatedClientEndpointMetadata)
313 {
314 cec = ((AnnotatedClientEndpointMetadata)metadata).getConfig();
315 }
316 else
317 {
318 cec = new EmptyClientEndpointConfig();
319 }
320 }
321 return new EndpointInstance(endpoint,cec,metadata);
322 }
323
324 @Override
325 public void setAsyncSendTimeout(long ms)
326 {
327 client.setAsyncWriteTimeout(ms);
328 }
329
330 @Override
331 public void setDefaultMaxBinaryMessageBufferSize(int max)
332 {
333
334 client.getPolicy().setMaxBinaryMessageSize(max);
335
336 client.setMaxBinaryMessageBufferSize(max);
337 }
338
339 @Override
340 public void setDefaultMaxSessionIdleTimeout(long ms)
341 {
342 client.setMaxIdleTimeout(ms);
343 }
344
345 @Override
346 public void setDefaultMaxTextMessageBufferSize(int max)
347 {
348
349 client.getPolicy().setMaxTextMessageSize(max);
350
351 client.setMaxTextMessageBufferSize(max);
352 }
353 }