From 884029f62e25d32a12ec315b9ea735f00c11e51d Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 5 May 2013 03:26:08 -0500 Subject: [PATCH] added optional wakelock argument to websocket for use with background push notifications. --- .../android_websockets/HybiParser.java | 18 +- .../android_websockets/WebSocketClient.java | 475 ++++++++++-------- 2 files changed, 274 insertions(+), 219 deletions(-) diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java index 1f83f2a..a5c62aa 100644 --- a/src/com/codebutler/android_websockets/HybiParser.java +++ b/src/com/codebutler/android_websockets/HybiParser.java @@ -30,6 +30,7 @@ package com.codebutler.android_websockets; +import android.os.PowerManager.WakeLock; import android.util.Log; import java.io.*; @@ -40,6 +41,8 @@ public class HybiParser { private static final String TAG = "HybiParser"; private WebSocketClient mClient; + + private WakeLock mWakeLock; private boolean mMasking = true; @@ -95,7 +98,12 @@ public HybiParser(WebSocketClient client) { mClient = client; } - private static byte[] mask(byte[] payload, byte[] mask, int offset) { + public HybiParser(WebSocketClient webSocketClient, WakeLock wakelock) { + mWakeLock = wakelock; + mClient = webSocketClient; + } + + private static byte[] mask(byte[] payload, byte[] mask, int offset) { if (mask.length == 0) return payload; for (int i = 0; i < payload.length - offset; i++) { @@ -127,11 +135,19 @@ public void start(HappyDataInputStream stream) throws IOException { mStage = 0; break; } + if(mWakeLock != null && mFinal) synchronized (mWakeLock) { + if(mWakeLock.isHeld())mWakeLock.release(); + } } mClient.getListener().onDisconnect(0, "EOF"); } private void parseOpcode(byte data) throws ProtocolError { + + if(mWakeLock != null) synchronized (mWakeLock) { + mWakeLock.acquire(); + } + boolean rsv1 = (data & RSV1) == RSV1; boolean rsv2 = (data & RSV2) == RSV2; boolean rsv3 = (data & RSV3) == RSV3; diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java index 82aadbe..9d203fb 100644 --- a/src/com/codebutler/android_websockets/WebSocketClient.java +++ b/src/com/codebutler/android_websockets/WebSocketClient.java @@ -2,6 +2,7 @@ import android.os.Handler; import android.os.HandlerThread; +import android.os.PowerManager.WakeLock; import android.text.TextUtils; import android.util.Base64; import android.util.Log; @@ -26,222 +27,260 @@ import java.util.List; public class WebSocketClient { - private static final String TAG = "WebSocketClient"; - - private URI mURI; - private Listener mListener; - private Socket mSocket; - private Thread mThread; - private HandlerThread mHandlerThread; - private Handler mHandler; - private List mExtraHeaders; - private HybiParser mParser; - private boolean mConnected; - - private final Object mSendLock = new Object(); - - private static TrustManager[] sTrustManagers; - - public static void setTrustManagers(TrustManager[] tm) { - sTrustManagers = tm; - } - - public WebSocketClient(URI uri, Listener listener, List extraHeaders) { - mURI = uri; - mListener = listener; - mExtraHeaders = extraHeaders; - mConnected = false; - mParser = new HybiParser(this); - - mHandlerThread = new HandlerThread("websocket-thread"); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); - } - - public Listener getListener() { - return mListener; - } - - public void connect() { - if (mThread != null && mThread.isAlive()) { - return; - } - - mThread = new Thread(new Runnable() { - @Override - public void run() { - try { - int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); - - String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); - if (!TextUtils.isEmpty(mURI.getQuery())) { - path += "?" + mURI.getQuery(); - } - - String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; - URI origin = new URI(originScheme, "//" + mURI.getHost(), null); - - SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); - mSocket = factory.createSocket(mURI.getHost(), port); - - PrintWriter out = new PrintWriter(mSocket.getOutputStream()); - out.print("GET " + path + " HTTP/1.1\r\n"); - out.print("Upgrade: websocket\r\n"); - out.print("Connection: Upgrade\r\n"); - out.print("Host: " + mURI.getHost() + "\r\n"); - out.print("Origin: " + origin.toString() + "\r\n"); - out.print("Sec-WebSocket-Key: " + createSecret() + "\r\n"); - out.print("Sec-WebSocket-Version: 13\r\n"); - if (mExtraHeaders != null) { - for (NameValuePair pair : mExtraHeaders) { - out.print(String.format("%s: %s\r\n", pair.getName(), pair.getValue())); - } - } - out.print("\r\n"); - out.flush(); - - HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); - - // Read HTTP response status line. - StatusLine statusLine = parseStatusLine(readLine(stream)); - if (statusLine == null) { - throw new HttpException("Received no reply from server."); - } else if (statusLine.getStatusCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) { - throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); - } - - // Read HTTP response headers. - String line; - while (!TextUtils.isEmpty(line = readLine(stream))) { - Header header = parseHeader(line); - if (header.getName().equals("Sec-WebSocket-Accept")) { - // FIXME: Verify the response... - } - } - - mListener.onConnect(); - - mConnected = true; - - // Now decode websocket frames. - mParser.start(stream); - - } catch (EOFException ex) { - Log.d(TAG, "WebSocket EOF!", ex); - mListener.onDisconnect(0, "EOF"); - mConnected = false; - - } catch (SSLException ex) { - // Connection reset by peer - Log.d(TAG, "Websocket SSL error!", ex); - mListener.onDisconnect(0, "SSL"); - mConnected = false; - - } catch (Exception ex) { - mListener.onError(ex); - } - } - }); - mThread.start(); - } - - public void disconnect() { - if (mSocket != null) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - mSocket.close(); - mSocket = null; - mConnected = false; - } catch (IOException ex) { - Log.d(TAG, "Error while disconnecting", ex); - mListener.onError(ex); - } - } - }); - } - } - - public void send(String data) { - sendFrame(mParser.frame(data)); - } - - public void send(byte[] data) { - sendFrame(mParser.frame(data)); - } - - public boolean isConnected() { - return mConnected; - } - - private StatusLine parseStatusLine(String line) { - if (TextUtils.isEmpty(line)) { - return null; - } - return BasicLineParser.parseStatusLine(line, new BasicLineParser()); - } - - private Header parseHeader(String line) { - return BasicLineParser.parseHeader(line, new BasicLineParser()); - } - - // Can't use BufferedReader because it buffers past the HTTP data. - private String readLine(HybiParser.HappyDataInputStream reader) throws IOException { - int readChar = reader.read(); - if (readChar == -1) { - return null; - } - StringBuilder string = new StringBuilder(""); - while (readChar != '\n') { - if (readChar != '\r') { - string.append((char) readChar); - } - - readChar = reader.read(); - if (readChar == -1) { - return null; - } - } - return string.toString(); - } - - private String createSecret() { - byte[] nonce = new byte[16]; - for (int i = 0; i < 16; i++) { - nonce[i] = (byte) (Math.random() * 256); - } - return Base64.encodeToString(nonce, Base64.DEFAULT).trim(); - } - - void sendFrame(final byte[] frame) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - synchronized (mSendLock) { - OutputStream outputStream = mSocket.getOutputStream(); - outputStream.write(frame); - outputStream.flush(); - } - } catch (IOException e) { - mListener.onError(e); - } - } - }); - } - - public interface Listener { - public void onConnect(); - public void onMessage(String message); - public void onMessage(byte[] data); - public void onDisconnect(int code, String reason); - public void onError(Exception error); - } - - private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { - SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, sTrustManagers, null); - return context.getSocketFactory(); - } + private static final String TAG = "WebSocketClient"; + + private URI mURI; + private Listener mListener; + private Socket mSocket; + private Thread mThread; + private HandlerThread mHandlerThread; + private Handler mHandler; + private List mExtraHeaders; + private HybiParser mParser; + private boolean mConnected; + private WakeLock mWakeLock; + + private final Object mSendLock = new Object(); + + private static TrustManager[] sTrustManagers; + + public static void setTrustManagers(TrustManager[] tm) { + sTrustManagers = tm; + } + + public WebSocketClient(URI uri, Listener listener, List extraHeaders) { + mURI = uri; + mListener = listener; + mExtraHeaders = extraHeaders; + mConnected = false; + mParser = new HybiParser(this); + + mHandlerThread = new HandlerThread("websocket-thread"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } + + public WebSocketClient(URI uri, Listener listener, List extraHeaders, WakeLock wakelock) { + mURI = uri; + mListener = listener; + mExtraHeaders = extraHeaders; + mConnected = false; + mParser = new HybiParser(this, wakelock); + + mHandlerThread = new HandlerThread("websocket-thread"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + mWakeLock = wakelock; + } + + public Listener getListener() { + return mListener; + } + + public void connect() { + if (mThread != null && mThread.isAlive()) { + return; + } + + mThread = new Thread(new Runnable() { + @Override + public void run() { + try { + if(mWakeLock != null) synchronized (mWakeLock) { + mWakeLock.acquire(); + } + int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); + + String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); + if (!TextUtils.isEmpty(mURI.getQuery())) { + path += "?" + mURI.getQuery(); + } + + String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; + URI origin = new URI(originScheme, "//" + mURI.getHost(), null); + + SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); + mSocket = factory.createSocket(mURI.getHost(), port); + + PrintWriter out = new PrintWriter(mSocket.getOutputStream()); + out.print("GET " + path + " HTTP/1.1\r\n"); + out.print("Upgrade: websocket\r\n"); + out.print("Connection: Upgrade\r\n"); + out.print("Host: " + mURI.getHost() + "\r\n"); + out.print("Origin: " + origin.toString() + "\r\n"); + out.print("Sec-WebSocket-Key: " + createSecret() + "\r\n"); + out.print("Sec-WebSocket-Version: 13\r\n"); + if (mExtraHeaders != null) { + for (NameValuePair pair : mExtraHeaders) { + out.print(String.format("%s: %s\r\n", pair.getName(), pair.getValue())); + } + } + out.print("\r\n"); + out.flush(); + + HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); + + // Read HTTP response status line. + StatusLine statusLine = parseStatusLine(readLine(stream)); + if (statusLine == null) { + throw new HttpException("Received no reply from server."); + } else if (statusLine.getStatusCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) { + throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); + } + + // Read HTTP response headers. + String line; + while (!TextUtils.isEmpty(line = readLine(stream))) { + Header header = parseHeader(line); + if (header.getName().equals("Sec-WebSocket-Accept")) { + // FIXME: Verify the response... + } + } + + mListener.onConnect(); + + mConnected = true; + if(mWakeLock != null && mWakeLock.isHeld()) mWakeLock.release(); + // Now decode websocket frames. + mParser.start(stream); + + } catch (EOFException ex) { + Log.d(TAG, "WebSocket EOF!", ex); + mListener.onDisconnect(0, "EOF"); + mConnected = false; + + } catch (SSLException ex) { + // Connection reset by peer + Log.d(TAG, "Websocket SSL error!", ex); + mListener.onDisconnect(0, "SSL"); + mConnected = false; + + } catch (Exception ex) { + mListener.onError(ex); + mConnected = false; + } finally { + if(mWakeLock != null && mWakeLock.isHeld()){ + mWakeLock.setReferenceCounted(false); + mWakeLock.release(); + mWakeLock.setReferenceCounted(true); + } + } + } + }); + mThread.start(); + } + + public void disconnect() { + if (mSocket != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + mSocket.close(); + mSocket = null; + mConnected = false; + } catch (IOException ex) { + Log.d(TAG, "Error while disconnecting", ex); + mListener.onError(ex); + } + } + }); + } + } + + public void send(String data) { + sendFrame(mParser.frame(data)); + } + + public void send(byte[] data) { + sendFrame(mParser.frame(data)); + } + + public boolean isConnected() { + return mConnected; + } + + private StatusLine parseStatusLine(String line) { + if (TextUtils.isEmpty(line)) { + return null; + } + return BasicLineParser.parseStatusLine(line, new BasicLineParser()); + } + + private Header parseHeader(String line) { + return BasicLineParser.parseHeader(line, new BasicLineParser()); + } + + // Can't use BufferedReader because it buffers past the HTTP data. + private String readLine(HybiParser.HappyDataInputStream reader) throws IOException { + int readChar = reader.read(); + if (readChar == -1) { + return null; + } + StringBuilder string = new StringBuilder(""); + while (readChar != '\n') { + if (readChar != '\r') { + string.append((char) readChar); + } + + readChar = reader.read(); + if (readChar == -1) { + return null; + } + } + return string.toString(); + } + + private String createSecret() { + byte[] nonce = new byte[16]; + for (int i = 0; i < 16; i++) { + nonce[i] = (byte) (Math.random() * 256); + } + return Base64.encodeToString(nonce, Base64.DEFAULT).trim(); + } + + void sendFrame(final byte[] frame) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + synchronized (mSendLock) { + if(mWakeLock != null) synchronized (mWakeLock) { + mWakeLock.acquire(); + } + OutputStream outputStream = mSocket.getOutputStream(); + outputStream.write(frame); + outputStream.flush(); + } + } catch (IOException e) { + mListener.onError(e); + mConnected = false; + if(mWakeLock != null) synchronized (mWakeLock) { + mWakeLock.setReferenceCounted(false); + } + } finally { + if(mWakeLock != null) synchronized (mWakeLock) { + if(mWakeLock.isHeld()) { + mWakeLock.release(); + mWakeLock.setReferenceCounted(true); + } + } + } + } + }); + } + + public interface Listener { + public void onConnect(); + public void onMessage(String message); + public void onMessage(byte[] data); + public void onDisconnect(int code, String reason); + public void onError(Exception error); + } + + private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, sTrustManagers, null); + return context.getSocketFactory(); + } }