1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/thirdparty/com/codebutler/android_websockets/WebSocketClient.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,252 @@ 1.4 +// This file was copied from: 1.5 +// https://github.com/koush/android-websockets/blob/master/src/com/codebutler/android_websockets/WebSocketClient.java 1.6 + 1.7 +package com.codebutler.android_websockets; 1.8 + 1.9 +import android.os.Handler; 1.10 +import android.os.HandlerThread; 1.11 +import android.text.TextUtils; 1.12 +import android.util.Base64; 1.13 +import android.util.Log; 1.14 +import org.apache.http.*; 1.15 +import org.apache.http.client.HttpResponseException; 1.16 +import org.apache.http.message.BasicLineParser; 1.17 +import org.apache.http.message.BasicNameValuePair; 1.18 + 1.19 +import javax.net.SocketFactory; 1.20 +import javax.net.ssl.SSLContext; 1.21 +import javax.net.ssl.SSLException; 1.22 +import javax.net.ssl.SSLSocketFactory; 1.23 +import javax.net.ssl.TrustManager; 1.24 +import java.io.EOFException; 1.25 +import java.io.IOException; 1.26 +import java.io.OutputStream; 1.27 +import java.io.PrintWriter; 1.28 +import java.net.Socket; 1.29 +import java.net.URI; 1.30 +import java.security.KeyManagementException; 1.31 +import java.security.NoSuchAlgorithmException; 1.32 +import java.util.List; 1.33 + 1.34 +public class WebSocketClient { 1.35 + private static final String TAG = "WebSocketClient"; 1.36 + 1.37 + private URI mURI; 1.38 + private Listener mListener; 1.39 + private Socket mSocket; 1.40 + private Thread mThread; 1.41 + private HandlerThread mHandlerThread; 1.42 + private Handler mHandler; 1.43 + private List<BasicNameValuePair> mExtraHeaders; 1.44 + private HybiParser mParser; 1.45 + private boolean mConnected; 1.46 + 1.47 + private final Object mSendLock = new Object(); 1.48 + 1.49 + private static TrustManager[] sTrustManagers; 1.50 + 1.51 + public static void setTrustManagers(TrustManager[] tm) { 1.52 + sTrustManagers = tm; 1.53 + } 1.54 + 1.55 + public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) { 1.56 + mURI = uri; 1.57 + mListener = listener; 1.58 + mExtraHeaders = extraHeaders; 1.59 + mConnected = false; 1.60 + mParser = new HybiParser(this); 1.61 + 1.62 + mHandlerThread = new HandlerThread("websocket-thread"); 1.63 + mHandlerThread.start(); 1.64 + mHandler = new Handler(mHandlerThread.getLooper()); 1.65 + } 1.66 + 1.67 + public Listener getListener() { 1.68 + return mListener; 1.69 + } 1.70 + 1.71 + public void connect() { 1.72 + if (mThread != null && mThread.isAlive()) { 1.73 + return; 1.74 + } 1.75 + 1.76 + mThread = new Thread(new Runnable() { 1.77 + @Override 1.78 + public void run() { 1.79 + try { 1.80 + int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); 1.81 + 1.82 + String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); 1.83 + if (!TextUtils.isEmpty(mURI.getQuery())) { 1.84 + path += "?" + mURI.getQuery(); 1.85 + } 1.86 + 1.87 + String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; 1.88 + URI origin = new URI(originScheme, "//" + mURI.getHost(), null); 1.89 + 1.90 + SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); 1.91 + mSocket = factory.createSocket(mURI.getHost(), port); 1.92 + 1.93 + PrintWriter out = new PrintWriter(mSocket.getOutputStream()); 1.94 + out.print("GET " + path + " HTTP/1.1\r\n"); 1.95 + out.print("Upgrade: websocket\r\n"); 1.96 + out.print("Connection: Upgrade\r\n"); 1.97 + out.print("Host: " + mURI.getHost() + "\r\n"); 1.98 + out.print("Origin: " + origin.toString() + "\r\n"); 1.99 + out.print("Sec-WebSocket-Key: " + createSecret() + "\r\n"); 1.100 + out.print("Sec-WebSocket-Version: 13\r\n"); 1.101 + if (mExtraHeaders != null) { 1.102 + for (NameValuePair pair : mExtraHeaders) { 1.103 + out.print(String.format("%s: %s\r\n", pair.getName(), pair.getValue())); 1.104 + } 1.105 + } 1.106 + out.print("\r\n"); 1.107 + out.flush(); 1.108 + 1.109 + HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); 1.110 + 1.111 + // Read HTTP response status line. 1.112 + StatusLine statusLine = parseStatusLine(readLine(stream)); 1.113 + if (statusLine == null) { 1.114 + throw new HttpException("Received no reply from server."); 1.115 + } else if (statusLine.getStatusCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) { 1.116 + throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); 1.117 + } 1.118 + 1.119 + // Read HTTP response headers. 1.120 + String line; 1.121 + while (!TextUtils.isEmpty(line = readLine(stream))) { 1.122 + Header header = parseHeader(line); 1.123 + if (header.getName().equals("Sec-WebSocket-Accept")) { 1.124 + // FIXME: Verify the response... 1.125 + } 1.126 + } 1.127 + 1.128 + mListener.onConnect(); 1.129 + 1.130 + mConnected = true; 1.131 + 1.132 + // Now decode websocket frames. 1.133 + mParser.start(stream); 1.134 + 1.135 + } catch (EOFException ex) { 1.136 + Log.d(TAG, "WebSocket EOF!", ex); 1.137 + mListener.onDisconnect(0, "EOF"); 1.138 + mConnected = false; 1.139 + 1.140 + } catch (SSLException ex) { 1.141 + // Connection reset by peer 1.142 + Log.d(TAG, "Websocket SSL error!", ex); 1.143 + mListener.onDisconnect(0, "SSL"); 1.144 + mConnected = false; 1.145 + 1.146 + } catch (Exception ex) { 1.147 + mListener.onError(ex); 1.148 + } 1.149 + } 1.150 + }); 1.151 + mThread.start(); 1.152 + } 1.153 + 1.154 + public void disconnect() { 1.155 + if (mSocket != null) { 1.156 + mHandler.post(new Runnable() { 1.157 + @Override 1.158 + public void run() { 1.159 + if (mSocket != null) { 1.160 + try { 1.161 + mSocket.close(); 1.162 + } catch (IOException ex) { 1.163 + Log.d(TAG, "Error while disconnecting", ex); 1.164 + mListener.onError(ex); 1.165 + } 1.166 + mSocket = null; 1.167 + } 1.168 + mConnected = false; 1.169 + } 1.170 + }); 1.171 + } 1.172 + } 1.173 + 1.174 + public void send(String data) { 1.175 + sendFrame(mParser.frame(data)); 1.176 + } 1.177 + 1.178 + public void send(byte[] data) { 1.179 + sendFrame(mParser.frame(data)); 1.180 + } 1.181 + 1.182 + public boolean isConnected() { 1.183 + return mConnected; 1.184 + } 1.185 + 1.186 + private StatusLine parseStatusLine(String line) { 1.187 + if (TextUtils.isEmpty(line)) { 1.188 + return null; 1.189 + } 1.190 + return BasicLineParser.parseStatusLine(line, new BasicLineParser()); 1.191 + } 1.192 + 1.193 + private Header parseHeader(String line) { 1.194 + return BasicLineParser.parseHeader(line, new BasicLineParser()); 1.195 + } 1.196 + 1.197 + // Can't use BufferedReader because it buffers past the HTTP data. 1.198 + private String readLine(HybiParser.HappyDataInputStream reader) throws IOException { 1.199 + int readChar = reader.read(); 1.200 + if (readChar == -1) { 1.201 + return null; 1.202 + } 1.203 + StringBuilder string = new StringBuilder(""); 1.204 + while (readChar != '\n') { 1.205 + if (readChar != '\r') { 1.206 + string.append((char) readChar); 1.207 + } 1.208 + 1.209 + readChar = reader.read(); 1.210 + if (readChar == -1) { 1.211 + return null; 1.212 + } 1.213 + } 1.214 + return string.toString(); 1.215 + } 1.216 + 1.217 + private String createSecret() { 1.218 + byte[] nonce = new byte[16]; 1.219 + for (int i = 0; i < 16; i++) { 1.220 + nonce[i] = (byte) (Math.random() * 256); 1.221 + } 1.222 + return Base64.encodeToString(nonce, Base64.DEFAULT).trim(); 1.223 + } 1.224 + 1.225 + void sendFrame(final byte[] frame) { 1.226 + mHandler.post(new Runnable() { 1.227 + @Override 1.228 + public void run() { 1.229 + try { 1.230 + synchronized (mSendLock) { 1.231 + OutputStream outputStream = mSocket.getOutputStream(); 1.232 + outputStream.write(frame); 1.233 + outputStream.flush(); 1.234 + } 1.235 + } catch (IOException e) { 1.236 + mListener.onError(e); 1.237 + } 1.238 + } 1.239 + }); 1.240 + } 1.241 + 1.242 + public interface Listener { 1.243 + public void onConnect(); 1.244 + public void onMessage(String message); 1.245 + public void onMessage(byte[] data); 1.246 + public void onDisconnect(int code, String reason); 1.247 + public void onError(Exception error); 1.248 + } 1.249 + 1.250 + private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { 1.251 + SSLContext context = SSLContext.getInstance("TLS"); 1.252 + context.init(null, sTrustManagers, null); 1.253 + return context.getSocketFactory(); 1.254 + } 1.255 +}