All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.websocket.WebSocketClient Maven / Gradle / Ivy

There is a newer version: 8.2.0.v20160908
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.websocket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.channels.ByteChannel;
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;


/* ------------------------------------------------------------ */
/**
 * 

{@link WebSocketClient} allows to create multiple connections to multiple destinations * that can speak the websocket protocol.

*

When creating websocket connections, {@link WebSocketClient} accepts a {@link WebSocket} * object (to receive events from the server), and returns a {@link WebSocket.Connection} to * send data to the server.

*

Example usage is as follows:

*
 *   WebSocketClientFactory factory = new WebSocketClientFactory();
 *   factory.start();
 *
 *   WebSocketClient client = factory.newWebSocketClient();
 *   // Configure the client
 *
 *   WebSocket.Connection connection = client.open(new URI("ws://127.0.0.1:8080/"), new WebSocket.OnTextMessage()
 *   {
 *     public void onOpen(Connection connection)
 *     {
 *       // open notification
 *     }
 *
 *     public void onClose(int closeCode, String message)
 *     {
 *       // close notification
 *     }
 *
 *     public void onMessage(String data)
 *     {
 *       // handle incoming message
 *     }
 *   }).get(5, TimeUnit.SECONDS);
 *
 *   connection.sendMessage("Hello World");
 * 
*/ public class WebSocketClient { private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getName()); private final WebSocketClientFactory _factory; private final Map _cookies=new ConcurrentHashMap(); private final List _extensions=new CopyOnWriteArrayList(); private String _origin; private String _protocol; private int _maxIdleTime=-1; private int _maxTextMessageSize=16*1024; private int _maxBinaryMessageSize=-1; private MaskGen _maskGen; private SocketAddress _bindAddress; /* ------------------------------------------------------------ */ /** *

Creates a WebSocketClient from a private WebSocketClientFactory.

*

This can be wasteful of resources if many clients are created.

* * @deprecated Use {@link WebSocketClientFactory#newWebSocketClient()} * @throws Exception if the private WebSocketClientFactory fails to start */ @Deprecated public WebSocketClient() throws Exception { _factory=new WebSocketClientFactory(); _factory.start(); _maskGen=_factory.getMaskGen(); } /* ------------------------------------------------------------ */ /** *

Creates a WebSocketClient with shared WebSocketClientFactory.

* * @param factory the shared {@link WebSocketClientFactory} */ public WebSocketClient(WebSocketClientFactory factory) { _factory=factory; _maskGen=_factory.getMaskGen(); } /* ------------------------------------------------------------ */ /** * @return The WebSocketClientFactory this client was created with. */ public WebSocketClientFactory getFactory() { return _factory; } /* ------------------------------------------------------------ */ /** * @return the address to bind the socket channel to * @see #setBindAddress(SocketAddress) */ public SocketAddress getBindAddress() { return _bindAddress; } /* ------------------------------------------------------------ */ /** * @param bindAddress the address to bind the socket channel to * @see #getBindAddress() */ public void setBindAddress(SocketAddress bindAddress) { this._bindAddress = bindAddress; } /* ------------------------------------------------------------ */ /** * @return The maxIdleTime in ms for connections opened by this client, * or -1 if the default from {@link WebSocketClientFactory#getSelectorManager()} is used. * @see #setMaxIdleTime(int) */ public int getMaxIdleTime() { return _maxIdleTime; } /* ------------------------------------------------------------ */ /** * @param maxIdleTime The max idle time in ms for connections opened by this client * @see #getMaxIdleTime() */ public void setMaxIdleTime(int maxIdleTime) { _maxIdleTime=maxIdleTime; } /* ------------------------------------------------------------ */ /** * @return The subprotocol string for connections opened by this client. * @see #setProtocol(String) */ public String getProtocol() { return _protocol; } /* ------------------------------------------------------------ */ /** * @param protocol The subprotocol string for connections opened by this client. * @see #getProtocol() */ public void setProtocol(String protocol) { _protocol = protocol; } /* ------------------------------------------------------------ */ /** * @return The origin URI of the client * @see #setOrigin(String) */ public String getOrigin() { return _origin; } /* ------------------------------------------------------------ */ /** * @param origin The origin URI of the client (eg "http://example.com") * @see #getOrigin() */ public void setOrigin(String origin) { _origin = origin; } /* ------------------------------------------------------------ */ /** *

Returns the map of the cookies that are sent during the initial HTTP handshake * that upgrades to the websocket protocol.

* @return The read-write cookie map */ public Map getCookies() { return _cookies; } /* ------------------------------------------------------------ */ /** * @return The list of websocket protocol extensions */ public List getExtensions() { return _extensions; } /* ------------------------------------------------------------ */ /** * @return the mask generator to use, or null if not mask generator should be used * @see #setMaskGen(MaskGen) */ public MaskGen getMaskGen() { return _maskGen; } /* ------------------------------------------------------------ */ /** * @param maskGen the mask generator to use, or null if not mask generator should be used * @see #getMaskGen() */ public void setMaskGen(MaskGen maskGen) { _maskGen = maskGen; } /* ------------------------------------------------------------ */ /** * @return The initial maximum text message size (in characters) for a connection */ public int getMaxTextMessageSize() { return _maxTextMessageSize; } /* ------------------------------------------------------------ */ /** * Set the initial maximum text message size for a connection. This can be changed by * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}. * @param maxTextMessageSize The default maximum text message size (in characters) for a connection */ public void setMaxTextMessageSize(int maxTextMessageSize) { _maxTextMessageSize = maxTextMessageSize; } /* ------------------------------------------------------------ */ /** * @return The initial maximum binary message size (in bytes) for a connection */ public int getMaxBinaryMessageSize() { return _maxBinaryMessageSize; } /* ------------------------------------------------------------ */ /** * Set the initial maximum binary message size for a connection. This can be changed by * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}. * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection */ public void setMaxBinaryMessageSize(int maxBinaryMessageSize) { _maxBinaryMessageSize = maxBinaryMessageSize; } /* ------------------------------------------------------------ */ /** *

Opens a websocket connection to the URI and blocks until the connection is accepted or there is an error.

* * @param uri The URI to connect to. * @param websocket The {@link WebSocket} instance to handle incoming events. * @param maxConnectTime The interval to wait for a successful connection * @param units the units of the maxConnectTime * @return A {@link WebSocket.Connection} * @throws IOException if the connection fails * @throws InterruptedException if the thread is interrupted * @throws TimeoutException if the timeout elapses before the connection is completed * @see #open(URI, WebSocket) */ public WebSocket.Connection open(URI uri, WebSocket websocket,long maxConnectTime,TimeUnit units) throws IOException, InterruptedException, TimeoutException { try { return open(uri,websocket).get(maxConnectTime,units); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof IOException) throw (IOException)cause; if (cause instanceof Error) throw (Error)cause; if (cause instanceof RuntimeException) throw (RuntimeException)cause; throw new RuntimeException(cause); } } /* ------------------------------------------------------------ */ /** *

Asynchronously opens a websocket connection and returns a {@link Future} to obtain the connection.

*

The caller must call {@link Future#get(long, TimeUnit)} if they wish to impose a connect timeout on the open.

* * @param uri The URI to connect to. * @param websocket The {@link WebSocket} instance to handle incoming events. * @return A {@link Future} to the {@link WebSocket.Connection} * @throws IOException if the connection fails * @see #open(URI, WebSocket, long, TimeUnit) */ public Future open(URI uri, WebSocket websocket) throws IOException { if (!_factory.isStarted()) throw new IllegalStateException("Factory !started"); __log.debug("Connecting to: {}", uri); InetSocketAddress address = toSocketAddress(uri); SocketChannel channel = null; try { channel = SocketChannel.open(); if (_bindAddress != null) channel.socket().bind(_bindAddress); channel.socket().setTcpNoDelay(true); WebSocketFuture holder = new WebSocketFuture(websocket,uri,this,channel); channel.configureBlocking(false); channel.connect(address); _factory.getSelectorManager().register(channel,holder); return holder; } catch (RuntimeException e) { // close the channel (prevent connection leak) IO.close(channel); // rethrow throw e; } catch(IOException e) { // close the channel (prevent connection leak) IO.close(channel); // rethrow throw e; } } public static InetSocketAddress toSocketAddress(URI uri) { String scheme = uri.getScheme(); if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme))) throw new IllegalArgumentException("Bad WebSocket scheme: " + scheme); int port = uri.getPort(); if (port == 0) throw new IllegalArgumentException("Bad WebSocket port: " + port); if (port < 0) port = "ws".equals(scheme) ? 80 : 443; return new InetSocketAddress(uri.getHost(), port); } /* ------------------------------------------------------------ */ /** The Future Websocket Connection. */ static class WebSocketFuture implements Future { final WebSocket _websocket; final URI _uri; final WebSocketClient _client; final CountDownLatch _done = new CountDownLatch(1); ByteChannel _channel; WebSocketConnection _connection; Throwable _exception; private WebSocketFuture(WebSocket websocket, URI uri, WebSocketClient client, ByteChannel channel) { _websocket=websocket; _uri=uri; _client=client; _channel=channel; } public void onConnection(WebSocketConnection connection) { try { _client.getFactory().addConnection(connection); connection.getConnection().setMaxTextMessageSize(_client.getMaxTextMessageSize()); connection.getConnection().setMaxBinaryMessageSize(_client.getMaxBinaryMessageSize()); WebSocketConnection con; synchronized (this) { if (_channel!=null) _connection=connection; con=_connection; } if (con!=null) { if (_websocket instanceof WebSocket.OnFrame) ((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)con.getConnection()); _websocket.onOpen(con.getConnection()); } } finally { _done.countDown(); } } public void handshakeFailed(Throwable ex) { try { ByteChannel channel=null; synchronized (this) { if (_channel!=null) { channel=_channel; _channel=null; _exception=ex; } } if (channel!=null) { if (ex instanceof ProtocolException) closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_PROTOCOL,ex.getMessage()); else closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,ex.getMessage()); } } finally { _done.countDown(); } } public Map getCookies() { return _client.getCookies(); } public String getProtocol() { return _client.getProtocol(); } public WebSocket getWebSocket() { return _websocket; } public URI getURI() { return _uri; } public int getMaxIdleTime() { return _client.getMaxIdleTime(); } public String getOrigin() { return _client.getOrigin(); } public MaskGen getMaskGen() { return _client.getMaskGen(); } @Override public String toString() { return "[" + _uri + ","+_websocket+"]@"+hashCode(); } public boolean cancel(boolean mayInterruptIfRunning) { try { ByteChannel channel=null; synchronized (this) { if (_connection==null && _exception==null && _channel!=null) { channel=_channel; _channel=null; } } if (channel!=null) { closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"cancelled"); return true; } return false; } finally { _done.countDown(); } } public boolean isCancelled() { synchronized (this) { return _channel==null && _connection==null; } } public boolean isDone() { synchronized (this) { return _connection!=null && _exception==null; } } public org.eclipse.jetty.websocket.WebSocket.Connection get() throws InterruptedException, ExecutionException { try { return get(Long.MAX_VALUE,TimeUnit.SECONDS); } catch(TimeoutException e) { throw new IllegalStateException("The universe has ended",e); } } public org.eclipse.jetty.websocket.WebSocket.Connection get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { _done.await(timeout,unit); ByteChannel channel=null; org.eclipse.jetty.websocket.WebSocket.Connection connection=null; Throwable exception; synchronized (this) { exception=_exception; if (_connection==null) { exception=_exception; channel=_channel; _channel=null; } else connection=_connection.getConnection(); } if (channel!=null) closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"timeout"); if (exception!=null) throw new ExecutionException(exception); if (connection!=null) return connection; throw new TimeoutException(); } private void closeChannel(ByteChannel channel,int code, String message) { try { _websocket.onClose(code,message); } catch(Exception e) { __log.warn(e); } try { channel.close(); } catch(IOException e) { __log.debug(e); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy