Java tutorial
/****************************************************************************** * Copyright 2011-2012 Tavendo GmbH * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.honeywell.printer.net.autobaln_websocket; import java.io.IOException; import java.lang.ref.WeakReference; import java.net.Socket; import java.net.URI; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.List; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import com.honeywell.printer.net.autobaln_websocket.WebSocket.ConnectionHandler.WebSocketCloseNotification; import org.apache.http.message.BasicNameValuePair; public class WebSocketConnection implements WebSocket { private static final String TAG = WebSocketConnection.class.getSimpleName(); private static final String WS_URI_SCHEME = "ws"; private static final String WSS_URI_SCHEME = "wss"; private static final String WS_WRITER = "WebSocketWriter"; private static final String WS_READER = "WebSocketReader"; private static Handler mHandler; private WebSocketReader mWebSocketReader; private WebSocketWriter mWebSocketWriter; private Socket mSocket; private SocketThread mSocketThread; private URI mWebSocketURI; private String[] mWebSocketSubprotocols; private WeakReference<ConnectionHandler> mWebSocketConnectionObserver; private WebSocketOptions mWebSocketOptions; private boolean mPreviousConnection = false; private List<BasicNameValuePair> mWsHeaders; public WebSocketConnection() { // Loger.print(TAG, "*****WebSocket connection created*********", Thread.currentThread()); this.mHandler = new ThreadHandler(this); } // // Forward to the writer thread public void sendTextMessage(String payload) { mWebSocketWriter.forward(new WebSocketMessage.TextMessage(payload)); } public void sendRawTextMessage(byte[] payload) { mWebSocketWriter.forward(new WebSocketMessage.RawTextMessage(payload)); } public void sendBinaryMessage(byte[] payload) { mWebSocketWriter.forward(new WebSocketMessage.BinaryMessage(payload)); } public boolean isConnected() { return mSocket != null && mSocket.isConnected() && !mSocket.isClosed(); } private void failConnection(WebSocketCloseNotification code, String reason) { Log.d(TAG, "fail connection [code = " + code + ", reason = " + reason); if (mWebSocketReader != null) { mWebSocketReader.quit(); try { mWebSocketReader.join(); } catch (InterruptedException e) { e.printStackTrace(); } } else { Log.d(TAG, "mReader already NULL"); } if (mWebSocketWriter != null) { mWebSocketWriter.forward(new WebSocketMessage.Quit()); try { mWebSocketWriter.join(); } catch (InterruptedException e) { e.printStackTrace(); } } else { Log.d(TAG, "mWriter already NULL"); } if (mSocket != null) { mSocketThread.getHandler().post(new Runnable() { @Override public void run() { mSocketThread.stopConnection(); } }); } else { Log.d(TAG, "mTransportChannel already NULL"); } mSocketThread.getHandler().post(new Runnable() { @Override public void run() { Looper.myLooper().quit(); } }); onClose(code, reason); Log.d(TAG, "worker threads stopped"); } public void connect(URI webSocketURI, ConnectionHandler connectionObserver) throws WebSocketException { connect(webSocketURI, connectionObserver, new WebSocketOptions()); } public void connect(URI webSocketURI, ConnectionHandler connectionObserver, WebSocketOptions options) throws WebSocketException { connect(webSocketURI, null, connectionObserver, options); } public void connect(URI webSocketURI, String[] subprotocols, ConnectionHandler connectionObserver, WebSocketOptions options) throws WebSocketException { if (isConnected()) { throw new WebSocketException("already connected"); } if (webSocketURI == null) { throw new WebSocketException("WebSockets URI null."); } else { this.mWebSocketURI = webSocketURI; if (!mWebSocketURI.getScheme().equals(WS_URI_SCHEME) && !mWebSocketURI.getScheme().equals(WSS_URI_SCHEME)) { throw new WebSocketException("unsupported scheme for WebSockets URI"); } this.mWebSocketSubprotocols = subprotocols; this.mWebSocketConnectionObserver = new WeakReference<ConnectionHandler>(connectionObserver); this.mWebSocketOptions = new WebSocketOptions(options); connect(); } } public void connect(String wsUri, String[] wsSubprotocols, WebSocket.ConnectionHandler wsHandler, WebSocketOptions options, List<BasicNameValuePair> headers) throws WebSocketException { try { this.mWebSocketURI = new URI(wsUri); if (!mWebSocketURI.getScheme().equals(WS_URI_SCHEME) && !mWebSocketURI.getScheme().equals(WSS_URI_SCHEME)) { throw new WebSocketException("unsupported scheme for WebSockets URI"); } this.mWebSocketSubprotocols = wsSubprotocols; this.mWebSocketConnectionObserver = new WeakReference<ConnectionHandler>(wsHandler); this.mWebSocketOptions = new WebSocketOptions(options); mWsHeaders = headers; connect(); } catch (Exception e) { e.printStackTrace(); } } public void disconnect() { if (mWebSocketWriter != null && mWebSocketWriter.isAlive()) { mWebSocketWriter.forward(new WebSocketMessage.Close()); } else { Log.d(TAG, "Could not send WebSocket Close .. writer already null"); } this.mPreviousConnection = false; } /** * Reconnect to the server with the latest options * * @return true if reconnection performed */ public boolean reconnect() { if (!isConnected() && (mWebSocketURI != null)) { connect(); return true; } return false; } private void connect() { mSocketThread = new SocketThread(mWebSocketURI, mWebSocketOptions); mSocketThread.start(); synchronized (mSocketThread) { try { mSocketThread.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } mSocketThread.getHandler().post(new Runnable() { @Override public void run() { mSocketThread.startConnection(); } }); synchronized (mSocketThread) { try { mSocketThread.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.mSocket = mSocketThread.getSocket(); if (mSocket == null) { onClose(WebSocketCloseNotification.CANNOT_CONNECT, mSocketThread.getFailureMessage()); // Loger.print(TAG, "ssd close ******** ", Thread.currentThread()); } else if (mSocket.isConnected()) { try { createReader(); createWriter(); WebSocketMessage.ClientHandshake clientHandshake = new WebSocketMessage.ClientHandshake( mWebSocketURI, null, mWebSocketSubprotocols); clientHandshake.mHeaderList = mWsHeaders; // Loger.print(TAG, "************** ssd start websocket ***********", Thread.currentThread()); mWebSocketWriter.forward(clientHandshake); } catch (Exception e) { onClose(WebSocketCloseNotification.INTERNAL_ERROR, e.getLocalizedMessage()); e.printStackTrace(); } } else { onClose(WebSocketCloseNotification.CANNOT_CONNECT, "could not connect to WebSockets server"); // Loger.print(TAG, "ssd close ******** ", Thread.currentThread()); } } /** * Perform reconnection * * @return true if reconnection was scheduled */ protected boolean scheduleReconnect() { /** * Reconnect only if: * - connection active (connected but not disconnected) * - has previous success connections * - reconnect interval is set */ int interval = mWebSocketOptions.getReconnectInterval(); boolean shouldReconnect = mSocket != null && mSocket.isConnected() && mPreviousConnection && (interval > 0); if (shouldReconnect) { Log.d(TAG, "WebSocket reconnection scheduled"); mHandler.postDelayed(new Runnable() { public void run() { Log.d(TAG, "WebSocket reconnecting..."); reconnect(); } }, interval); } return shouldReconnect; } /** * Common close handler * * @param code Close code. * @param reason Close reason (human-readable). */ private void onClose(WebSocketCloseNotification code, String reason) { boolean reconnecting = false; if ((code == WebSocketCloseNotification.CANNOT_CONNECT) || (code == WebSocketCloseNotification.CONNECTION_LOST)) { reconnecting = scheduleReconnect(); } ConnectionHandler webSocketObserver = mWebSocketConnectionObserver.get(); if (webSocketObserver != null) { try { if (reconnecting) { webSocketObserver.onClose(ConnectionHandler.WebSocketCloseNotification.RECONNECT, reason); } else { webSocketObserver.onClose(code, reason); } } catch (Exception e) { e.printStackTrace(); } } else { Log.d(TAG, "WebSocketObserver null"); } } protected void processAppMessage(Object message) { } /** * Create WebSockets background writer. */ protected void createWriter() { mWebSocketWriter = new WebSocketWriter(mHandler, mSocket, mWebSocketOptions, WS_WRITER); mWebSocketWriter.start(); synchronized (mWebSocketWriter) { try { mWebSocketWriter.wait(); } catch (InterruptedException e) { } } // Loger.print(TAG, "WebSocket writer created and started.", Thread.currentThread()); } /** * Create WebSockets background reader. */ protected void createReader() { mWebSocketReader = new WebSocketReader(mHandler, mSocket, mWebSocketOptions, WS_READER); mWebSocketReader.start(); synchronized (mWebSocketReader) { try { mWebSocketReader.wait(); } catch (InterruptedException e) { } } // Loger.print(TAG, "WebSocket reader created and started.", Thread.currentThread()); } private void handleMessage(Message message) { ConnectionHandler webSocketObserver = mWebSocketConnectionObserver.get(); if (message.obj instanceof WebSocketMessage.TextMessage) { WebSocketMessage.TextMessage textMessage = (WebSocketMessage.TextMessage) message.obj; if (webSocketObserver != null) { webSocketObserver.onTextMessage(textMessage.mPayload); } else { Log.d(TAG, "could not call onTextMessage() .. handler already NULL"); } } else if (message.obj instanceof WebSocketMessage.RawTextMessage) { WebSocketMessage.RawTextMessage rawTextMessage = (WebSocketMessage.RawTextMessage) message.obj; if (webSocketObserver != null) { webSocketObserver.onRawTextMessage(rawTextMessage.mPayload); } else { Log.d(TAG, "could not call onRawTextMessage() .. handler already NULL"); } } else if (message.obj instanceof WebSocketMessage.BinaryMessage) { WebSocketMessage.BinaryMessage binaryMessage = (WebSocketMessage.BinaryMessage) message.obj; if (webSocketObserver != null) { webSocketObserver.onBinaryMessage(binaryMessage.mPayload); } else { Log.d(TAG, "could not call onBinaryMessage() .. handler already NULL"); } } else if (message.obj instanceof WebSocketMessage.Ping) { WebSocketMessage.Ping ping = (WebSocketMessage.Ping) message.obj; Log.d(TAG, "WebSockets Ping received"); WebSocketMessage.Pong pong = new WebSocketMessage.Pong(); pong.mPayload = ping.mPayload; mWebSocketWriter.forward(pong); } else if (message.obj instanceof WebSocketMessage.Pong) { WebSocketMessage.Pong pong = (WebSocketMessage.Pong) message.obj; Log.d(TAG, "WebSockets Pong received" + pong.mPayload); } else if (message.obj instanceof WebSocketMessage.Close) { WebSocketMessage.Close close = (WebSocketMessage.Close) message.obj; Log.d(TAG, "WebSockets Close received (" + close.getCode() + " - " + close.getReason() + ")"); mWebSocketWriter.forward(new WebSocketMessage.Close(WebSocketMessage.WebSocketCloseCode.NORMAL)); } else if (message.obj instanceof WebSocketMessage.ServerHandshake) { WebSocketMessage.ServerHandshake serverHandshake = (WebSocketMessage.ServerHandshake) message.obj; Log.d(TAG, "opening handshake received"); if (serverHandshake.mSuccess) { if (webSocketObserver != null) { webSocketObserver.onOpen(); } else { Log.d(TAG, "could not call onOpen() .. handler already NULL"); } mPreviousConnection = true; } } else if (message.obj instanceof WebSocketMessage.ConnectionLost) { // WebSocketMessage.ConnectionLost connectionLost = (WebSocketMessage.ConnectionLost) message.obj; failConnection(WebSocketCloseNotification.CONNECTION_LOST, "WebSockets connection lost"); } else if (message.obj instanceof WebSocketMessage.ProtocolViolation) { // WebSocketMessage.ProtocolViolation protocolViolation = (WebSocketMessage.ProtocolViolation) message.obj; failConnection(WebSocketCloseNotification.PROTOCOL_ERROR, "WebSockets protocol violation"); } else if (message.obj instanceof WebSocketMessage.Error) { WebSocketMessage.Error error = (WebSocketMessage.Error) message.obj; failConnection(WebSocketCloseNotification.INTERNAL_ERROR, "WebSockets internal error (" + error.mException.toString() + ")"); } else if (message.obj instanceof WebSocketMessage.ServerError) { WebSocketMessage.ServerError error = (WebSocketMessage.ServerError) message.obj; failConnection(WebSocketCloseNotification.SERVER_ERROR, "Server error " + error.mStatusCode + " (" + error.mStatusMessage + ")"); } else { processAppMessage(message.obj); } } public static class SocketThread extends Thread { private static final String WS_CONNECTOR = "WebSocketConnector"; private final URI mWebSocketURI; private Socket mSocket = null; private String mFailureMessage = null; private Handler mHandler; public SocketThread(URI uri, WebSocketOptions options) { this.setName(WS_CONNECTOR); this.mWebSocketURI = uri; } @Override public void run() { Looper.prepare(); this.mHandler = new Handler(); synchronized (this) { notifyAll(); } Looper.loop(); Log.d(TAG, "SocketThread exited."); } public void startConnection() { try { String host = mWebSocketURI.getHost(); int port = mWebSocketURI.getPort(); if (port == -1) { if (mWebSocketURI.getScheme().equals(WSS_URI_SCHEME)) { port = 443; } else { port = 80; } } SocketFactory factory = null; if (mWebSocketURI.getScheme().equalsIgnoreCase(WSS_URI_SCHEME)) { SSLContext sc = SSLContext.getInstance("TLSv1.2"); sc.init(null, null, null); factory = new Tls12SocketFactory(sc.getSocketFactory()); } else { factory = SocketFactory.getDefault(); } // Do not replace host string with InetAddress or you lose automatic host name verification this.mSocket = factory.createSocket(host, port); } catch (IOException e) { this.mFailureMessage = e.getLocalizedMessage(); e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } synchronized (this) { notifyAll(); } } public void stopConnection() { try { mSocket.close(); this.mSocket = null; } catch (IOException e) { this.mFailureMessage = e.getLocalizedMessage(); } } public Handler getHandler() { return mHandler; } public Socket getSocket() { return mSocket; } public String getFailureMessage() { return mFailureMessage; } } private static class ThreadHandler extends Handler { private final WeakReference<WebSocketConnection> mWebSocketConnection; public ThreadHandler(WebSocketConnection webSocketConnection) { super(); this.mWebSocketConnection = new WeakReference<WebSocketConnection>(webSocketConnection); } @Override public void handleMessage(Message message) { if (mWebSocketConnection != null) { WebSocketConnection webSocketConnection = mWebSocketConnection.get(); if (webSocketConnection != null) { webSocketConnection.handleMessage(message); } } } } }