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