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.EndpointConfig;
37 import javax.websocket.Extension;
38 import javax.websocket.Session;
39 import javax.websocket.WebSocketContainer;
40
41 import org.eclipse.jetty.io.ByteBufferPool;
42 import org.eclipse.jetty.util.DecoratedObjectFactory;
43 import org.eclipse.jetty.util.component.ContainerLifeCycle;
44 import org.eclipse.jetty.util.log.Log;
45 import org.eclipse.jetty.util.log.Logger;
46 import org.eclipse.jetty.util.ssl.SslContextFactory;
47 import org.eclipse.jetty.util.thread.ShutdownThread;
48 import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
49 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
50 import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
51 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
52 import org.eclipse.jetty.websocket.client.WebSocketClient;
53 import org.eclipse.jetty.websocket.client.io.UpgradeListener;
54 import org.eclipse.jetty.websocket.common.WebSocketSession;
55 import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope;
56 import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
57 import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
58 import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
59 import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
60 import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
61 import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
62 import org.eclipse.jetty.websocket.jsr356.encoders.PrimitiveEncoderMetadataSet;
63 import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
64 import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEventDriverFactory;
65 import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
66
67
68
69
70
71
72 public class ClientContainer extends ContainerLifeCycle implements WebSocketContainer, WebSocketContainerScope
73 {
74 private static final Logger LOG = Log.getLogger(ClientContainer.class);
75
76
77 private final WebSocketContainerScope scopeDelegate;
78
79 private final DecoderFactory decoderFactory;
80
81 private final EncoderFactory encoderFactory;
82
83 private final Map<Class<?>, EndpointMetadata> endpointClientMetadataCache;
84
85 private WebSocketClient client;
86
87 public ClientContainer()
88 {
89
90 this(new SimpleContainerScope(WebSocketPolicy.newClientPolicy()));
91 client.setDaemon(true);
92 }
93
94 public ClientContainer(WebSocketContainerScope scope)
95 {
96 boolean trustAll = Boolean.getBoolean("org.eclipse.jetty.websocket.jsr356.ssl-trust-all");
97
98 this.scopeDelegate = scope;
99 client = new WebSocketClient(scope, new SslContextFactory(trustAll));
100 client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
101 client.setSessionFactory(new JsrSessionFactory(this));
102 addBean(client);
103
104 this.endpointClientMetadataCache = new ConcurrentHashMap<>();
105 this.decoderFactory = new DecoderFactory(this,PrimitiveDecoderMetadataSet.INSTANCE);
106 this.encoderFactory = new EncoderFactory(this,PrimitiveEncoderMetadataSet.INSTANCE);
107
108 ShutdownThread.register(this);
109 }
110
111 private Session connect(EndpointInstance instance, URI path) throws IOException
112 {
113 Objects.requireNonNull(instance,"EndpointInstance cannot be null");
114 Objects.requireNonNull(path,"Path cannot be null");
115
116 ClientEndpointConfig config = (ClientEndpointConfig)instance.getConfig();
117 ClientUpgradeRequest req = new ClientUpgradeRequest();
118 UpgradeListener upgradeListener = null;
119
120 for (Extension ext : config.getExtensions())
121 {
122 req.addExtensions(new JsrExtensionConfig(ext));
123 }
124
125 if (config.getPreferredSubprotocols().size() > 0)
126 {
127 req.setSubProtocols(config.getPreferredSubprotocols());
128 }
129
130 if (config.getConfigurator() != null)
131 {
132 upgradeListener = new JsrUpgradeListener(config.getConfigurator());
133 }
134
135 Future<org.eclipse.jetty.websocket.api.Session> futSess = client.connect(instance,path,req,upgradeListener);
136 try
137 {
138 return (JsrSession)futSess.get();
139 }
140 catch (InterruptedException e)
141 {
142 throw new IOException("Connect failure",e);
143 }
144 catch (ExecutionException e)
145 {
146
147 Throwable cause = e.getCause();
148
149 if (cause instanceof IOException)
150 {
151
152 throw (IOException)cause;
153 }
154 else
155 {
156 throw new IOException("Connect failure",cause);
157 }
158 }
159 }
160
161 @Override
162 public Session connectToServer(Class<? extends Endpoint> endpointClass, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
163 {
164 EndpointInstance instance = newClientEndpointInstance(endpointClass,config);
165 return connect(instance,path);
166 }
167
168 @Override
169 public Session connectToServer(Class<?> annotatedEndpointClass, URI path) throws DeploymentException, IOException
170 {
171 EndpointInstance instance = newClientEndpointInstance(annotatedEndpointClass,null);
172 return connect(instance,path);
173 }
174
175 @Override
176 public Session connectToServer(Endpoint endpoint, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
177 {
178 EndpointInstance instance = newClientEndpointInstance(endpoint,config);
179 return connect(instance,path);
180 }
181
182 @Override
183 public Session connectToServer(Object endpoint, URI path) throws DeploymentException, IOException
184 {
185 EndpointInstance instance = newClientEndpointInstance(endpoint,null);
186 return connect(instance,path);
187 }
188
189 @Override
190 protected void doStart() throws Exception
191 {
192 super.doStart();
193
194
195 EmptyClientEndpointConfig empty = new EmptyClientEndpointConfig();
196 this.decoderFactory.init(empty);
197 this.encoderFactory.init(empty);
198 }
199
200 @Override
201 protected void doStop() throws Exception
202 {
203 ShutdownThread.deregister(this);
204 endpointClientMetadataCache.clear();
205 super.doStop();
206 }
207
208 @Override
209 public ByteBufferPool getBufferPool()
210 {
211 return scopeDelegate.getBufferPool();
212 }
213
214 public WebSocketClient getClient()
215 {
216 return client;
217 }
218
219 public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint, EndpointConfig config)
220 {
221 synchronized (endpointClientMetadataCache)
222 {
223 EndpointMetadata metadata = endpointClientMetadataCache.get(endpoint);
224
225 if (metadata != null)
226 {
227 return metadata;
228 }
229
230 ClientEndpoint anno = endpoint.getAnnotation(ClientEndpoint.class);
231 if (anno != null)
232 {
233
234 AnnotatedClientEndpointMetadata annoMetadata = new AnnotatedClientEndpointMetadata(this,endpoint);
235 AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(annoMetadata);
236 scanner.scan();
237 metadata = annoMetadata;
238 }
239 else if (Endpoint.class.isAssignableFrom(endpoint))
240 {
241
242 @SuppressWarnings("unchecked")
243 Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
244 metadata = new SimpleEndpointMetadata(eendpoint,config);
245 }
246 else
247 {
248 StringBuilder err = new StringBuilder();
249 err.append("Not a recognized websocket [");
250 err.append(endpoint.getName());
251 err.append("] does not extend @").append(ClientEndpoint.class.getName());
252 err.append(" or extend from ").append(Endpoint.class.getName());
253 throw new InvalidWebSocketException(err.toString());
254 }
255
256 endpointClientMetadataCache.put(endpoint,metadata);
257 return metadata;
258 }
259 }
260
261 public DecoderFactory getDecoderFactory()
262 {
263 return decoderFactory;
264 }
265
266 @Override
267 public long getDefaultAsyncSendTimeout()
268 {
269 return client.getAsyncWriteTimeout();
270 }
271
272 @Override
273 public int getDefaultMaxBinaryMessageBufferSize()
274 {
275 return client.getMaxBinaryMessageBufferSize();
276 }
277
278 @Override
279 public long getDefaultMaxSessionIdleTimeout()
280 {
281 return client.getMaxIdleTimeout();
282 }
283
284 @Override
285 public int getDefaultMaxTextMessageBufferSize()
286 {
287 return client.getMaxTextMessageBufferSize();
288 }
289
290 public EncoderFactory getEncoderFactory()
291 {
292 return encoderFactory;
293 }
294
295 @Override
296 public Executor getExecutor()
297 {
298 return scopeDelegate.getExecutor();
299 }
300
301 @Override
302 public Set<Extension> getInstalledExtensions()
303 {
304 Set<Extension> ret = new HashSet<>();
305 ExtensionFactory extensions = client.getExtensionFactory();
306
307 for (String name : extensions.getExtensionNames())
308 {
309 ret.add(new JsrExtension(name));
310 }
311
312 return ret;
313 }
314
315 @Override
316 public DecoratedObjectFactory getObjectFactory()
317 {
318 return scopeDelegate.getObjectFactory();
319 }
320
321
322
323
324
325 public Set<Session> getOpenSessions()
326 {
327 return new HashSet<>(getBeans(Session.class));
328 }
329
330 @Override
331 public WebSocketPolicy getPolicy()
332 {
333 return scopeDelegate.getPolicy();
334 }
335
336 @Override
337 public SslContextFactory getSslContextFactory()
338 {
339 return scopeDelegate.getSslContextFactory();
340 }
341
342 private EndpointInstance newClientEndpointInstance(Class<?> endpointClass, ClientEndpointConfig config)
343 {
344 try
345 {
346 return newClientEndpointInstance(endpointClass.newInstance(),config);
347 }
348 catch (InstantiationException | IllegalAccessException e)
349 {
350 throw new InvalidWebSocketException("Unable to instantiate websocket: " + endpointClass.getClass());
351 }
352 }
353
354 public EndpointInstance newClientEndpointInstance(Object endpoint, ClientEndpointConfig config)
355 {
356 EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config);
357 ClientEndpointConfig cec = config;
358 if (config == null)
359 {
360 if (metadata instanceof AnnotatedClientEndpointMetadata)
361 {
362 cec = ((AnnotatedClientEndpointMetadata)metadata).getConfig();
363 }
364 else
365 {
366 cec = new EmptyClientEndpointConfig();
367 }
368 }
369 return new EndpointInstance(endpoint,cec,metadata);
370 }
371
372 @Override
373 public void onSessionClosed(WebSocketSession session)
374 {
375 if (session instanceof Session)
376 {
377 removeBean(session);
378 }
379 else
380 {
381 LOG.warn("JSR356 Implementation should not be mixed with native implementation: Expected {} to implement {}",session.getClass().getName(),
382 Session.class.getName());
383 }
384 }
385
386 @Override
387 public void onSessionOpened(WebSocketSession session)
388 {
389 if (session instanceof Session)
390 {
391 addManaged(session);
392 }
393 else
394 {
395 LOG.warn("JSR356 Implementation should not be mixed with native implementation: Expected {} to implement {}",session.getClass().getName(),
396 Session.class.getName());
397 }
398 }
399
400 @Override
401 public void setAsyncSendTimeout(long ms)
402 {
403 client.setAsyncWriteTimeout(ms);
404 }
405
406 @Override
407 public void setDefaultMaxBinaryMessageBufferSize(int max)
408 {
409
410 client.getPolicy().setMaxBinaryMessageSize(max);
411
412 client.setMaxBinaryMessageBufferSize(max);
413 }
414
415 @Override
416 public void setDefaultMaxSessionIdleTimeout(long ms)
417 {
418 client.setMaxIdleTimeout(ms);
419 }
420
421 @Override
422 public void setDefaultMaxTextMessageBufferSize(int max)
423 {
424
425 client.getPolicy().setMaxTextMessageSize(max);
426
427 client.setMaxTextMessageBufferSize(max);
428 }
429 }