michael@0: // This file was copied from: michael@0: // https://github.com/koush/android-websockets/blob/master/src/com/codebutler/android_websockets/WebSocketClient.java michael@0: michael@0: package com.codebutler.android_websockets; michael@0: michael@0: import android.os.Handler; michael@0: import android.os.HandlerThread; michael@0: import android.text.TextUtils; michael@0: import android.util.Base64; michael@0: import android.util.Log; michael@0: import org.apache.http.*; michael@0: import org.apache.http.client.HttpResponseException; michael@0: import org.apache.http.message.BasicLineParser; michael@0: import org.apache.http.message.BasicNameValuePair; michael@0: michael@0: import javax.net.SocketFactory; michael@0: import javax.net.ssl.SSLContext; michael@0: import javax.net.ssl.SSLException; michael@0: import javax.net.ssl.SSLSocketFactory; michael@0: import javax.net.ssl.TrustManager; michael@0: import java.io.EOFException; michael@0: import java.io.IOException; michael@0: import java.io.OutputStream; michael@0: import java.io.PrintWriter; michael@0: import java.net.Socket; michael@0: import java.net.URI; michael@0: import java.security.KeyManagementException; michael@0: import java.security.NoSuchAlgorithmException; michael@0: import java.util.List; michael@0: michael@0: public class WebSocketClient { michael@0: private static final String TAG = "WebSocketClient"; michael@0: michael@0: private URI mURI; michael@0: private Listener mListener; michael@0: private Socket mSocket; michael@0: private Thread mThread; michael@0: private HandlerThread mHandlerThread; michael@0: private Handler mHandler; michael@0: private List mExtraHeaders; michael@0: private HybiParser mParser; michael@0: private boolean mConnected; michael@0: michael@0: private final Object mSendLock = new Object(); michael@0: michael@0: private static TrustManager[] sTrustManagers; michael@0: michael@0: public static void setTrustManagers(TrustManager[] tm) { michael@0: sTrustManagers = tm; michael@0: } michael@0: michael@0: public WebSocketClient(URI uri, Listener listener, List extraHeaders) { michael@0: mURI = uri; michael@0: mListener = listener; michael@0: mExtraHeaders = extraHeaders; michael@0: mConnected = false; michael@0: mParser = new HybiParser(this); michael@0: michael@0: mHandlerThread = new HandlerThread("websocket-thread"); michael@0: mHandlerThread.start(); michael@0: mHandler = new Handler(mHandlerThread.getLooper()); michael@0: } michael@0: michael@0: public Listener getListener() { michael@0: return mListener; michael@0: } michael@0: michael@0: public void connect() { michael@0: if (mThread != null && mThread.isAlive()) { michael@0: return; michael@0: } michael@0: michael@0: mThread = new Thread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); michael@0: michael@0: String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); michael@0: if (!TextUtils.isEmpty(mURI.getQuery())) { michael@0: path += "?" + mURI.getQuery(); michael@0: } michael@0: michael@0: String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; michael@0: URI origin = new URI(originScheme, "//" + mURI.getHost(), null); michael@0: michael@0: SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); michael@0: mSocket = factory.createSocket(mURI.getHost(), port); michael@0: michael@0: PrintWriter out = new PrintWriter(mSocket.getOutputStream()); michael@0: out.print("GET " + path + " HTTP/1.1\r\n"); michael@0: out.print("Upgrade: websocket\r\n"); michael@0: out.print("Connection: Upgrade\r\n"); michael@0: out.print("Host: " + mURI.getHost() + "\r\n"); michael@0: out.print("Origin: " + origin.toString() + "\r\n"); michael@0: out.print("Sec-WebSocket-Key: " + createSecret() + "\r\n"); michael@0: out.print("Sec-WebSocket-Version: 13\r\n"); michael@0: if (mExtraHeaders != null) { michael@0: for (NameValuePair pair : mExtraHeaders) { michael@0: out.print(String.format("%s: %s\r\n", pair.getName(), pair.getValue())); michael@0: } michael@0: } michael@0: out.print("\r\n"); michael@0: out.flush(); michael@0: michael@0: HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); michael@0: michael@0: // Read HTTP response status line. michael@0: StatusLine statusLine = parseStatusLine(readLine(stream)); michael@0: if (statusLine == null) { michael@0: throw new HttpException("Received no reply from server."); michael@0: } else if (statusLine.getStatusCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) { michael@0: throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); michael@0: } michael@0: michael@0: // Read HTTP response headers. michael@0: String line; michael@0: while (!TextUtils.isEmpty(line = readLine(stream))) { michael@0: Header header = parseHeader(line); michael@0: if (header.getName().equals("Sec-WebSocket-Accept")) { michael@0: // FIXME: Verify the response... michael@0: } michael@0: } michael@0: michael@0: mListener.onConnect(); michael@0: michael@0: mConnected = true; michael@0: michael@0: // Now decode websocket frames. michael@0: mParser.start(stream); michael@0: michael@0: } catch (EOFException ex) { michael@0: Log.d(TAG, "WebSocket EOF!", ex); michael@0: mListener.onDisconnect(0, "EOF"); michael@0: mConnected = false; michael@0: michael@0: } catch (SSLException ex) { michael@0: // Connection reset by peer michael@0: Log.d(TAG, "Websocket SSL error!", ex); michael@0: mListener.onDisconnect(0, "SSL"); michael@0: mConnected = false; michael@0: michael@0: } catch (Exception ex) { michael@0: mListener.onError(ex); michael@0: } michael@0: } michael@0: }); michael@0: mThread.start(); michael@0: } michael@0: michael@0: public void disconnect() { michael@0: if (mSocket != null) { michael@0: mHandler.post(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: if (mSocket != null) { michael@0: try { michael@0: mSocket.close(); michael@0: } catch (IOException ex) { michael@0: Log.d(TAG, "Error while disconnecting", ex); michael@0: mListener.onError(ex); michael@0: } michael@0: mSocket = null; michael@0: } michael@0: mConnected = false; michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: michael@0: public void send(String data) { michael@0: sendFrame(mParser.frame(data)); michael@0: } michael@0: michael@0: public void send(byte[] data) { michael@0: sendFrame(mParser.frame(data)); michael@0: } michael@0: michael@0: public boolean isConnected() { michael@0: return mConnected; michael@0: } michael@0: michael@0: private StatusLine parseStatusLine(String line) { michael@0: if (TextUtils.isEmpty(line)) { michael@0: return null; michael@0: } michael@0: return BasicLineParser.parseStatusLine(line, new BasicLineParser()); michael@0: } michael@0: michael@0: private Header parseHeader(String line) { michael@0: return BasicLineParser.parseHeader(line, new BasicLineParser()); michael@0: } michael@0: michael@0: // Can't use BufferedReader because it buffers past the HTTP data. michael@0: private String readLine(HybiParser.HappyDataInputStream reader) throws IOException { michael@0: int readChar = reader.read(); michael@0: if (readChar == -1) { michael@0: return null; michael@0: } michael@0: StringBuilder string = new StringBuilder(""); michael@0: while (readChar != '\n') { michael@0: if (readChar != '\r') { michael@0: string.append((char) readChar); michael@0: } michael@0: michael@0: readChar = reader.read(); michael@0: if (readChar == -1) { michael@0: return null; michael@0: } michael@0: } michael@0: return string.toString(); michael@0: } michael@0: michael@0: private String createSecret() { michael@0: byte[] nonce = new byte[16]; michael@0: for (int i = 0; i < 16; i++) { michael@0: nonce[i] = (byte) (Math.random() * 256); michael@0: } michael@0: return Base64.encodeToString(nonce, Base64.DEFAULT).trim(); michael@0: } michael@0: michael@0: void sendFrame(final byte[] frame) { michael@0: mHandler.post(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: synchronized (mSendLock) { michael@0: OutputStream outputStream = mSocket.getOutputStream(); michael@0: outputStream.write(frame); michael@0: outputStream.flush(); michael@0: } michael@0: } catch (IOException e) { michael@0: mListener.onError(e); michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: michael@0: public interface Listener { michael@0: public void onConnect(); michael@0: public void onMessage(String message); michael@0: public void onMessage(byte[] data); michael@0: public void onDisconnect(int code, String reason); michael@0: public void onError(Exception error); michael@0: } michael@0: michael@0: private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { michael@0: SSLContext context = SSLContext.getInstance("TLS"); michael@0: context.init(null, sTrustManagers, null); michael@0: return context.getSocketFactory(); michael@0: } michael@0: }