|
1 // This file was copied from: |
|
2 // https://github.com/koush/android-websockets/blob/master/src/com/codebutler/android_websockets/WebSocketClient.java |
|
3 |
|
4 package com.codebutler.android_websockets; |
|
5 |
|
6 import android.os.Handler; |
|
7 import android.os.HandlerThread; |
|
8 import android.text.TextUtils; |
|
9 import android.util.Base64; |
|
10 import android.util.Log; |
|
11 import org.apache.http.*; |
|
12 import org.apache.http.client.HttpResponseException; |
|
13 import org.apache.http.message.BasicLineParser; |
|
14 import org.apache.http.message.BasicNameValuePair; |
|
15 |
|
16 import javax.net.SocketFactory; |
|
17 import javax.net.ssl.SSLContext; |
|
18 import javax.net.ssl.SSLException; |
|
19 import javax.net.ssl.SSLSocketFactory; |
|
20 import javax.net.ssl.TrustManager; |
|
21 import java.io.EOFException; |
|
22 import java.io.IOException; |
|
23 import java.io.OutputStream; |
|
24 import java.io.PrintWriter; |
|
25 import java.net.Socket; |
|
26 import java.net.URI; |
|
27 import java.security.KeyManagementException; |
|
28 import java.security.NoSuchAlgorithmException; |
|
29 import java.util.List; |
|
30 |
|
31 public class WebSocketClient { |
|
32 private static final String TAG = "WebSocketClient"; |
|
33 |
|
34 private URI mURI; |
|
35 private Listener mListener; |
|
36 private Socket mSocket; |
|
37 private Thread mThread; |
|
38 private HandlerThread mHandlerThread; |
|
39 private Handler mHandler; |
|
40 private List<BasicNameValuePair> mExtraHeaders; |
|
41 private HybiParser mParser; |
|
42 private boolean mConnected; |
|
43 |
|
44 private final Object mSendLock = new Object(); |
|
45 |
|
46 private static TrustManager[] sTrustManagers; |
|
47 |
|
48 public static void setTrustManagers(TrustManager[] tm) { |
|
49 sTrustManagers = tm; |
|
50 } |
|
51 |
|
52 public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) { |
|
53 mURI = uri; |
|
54 mListener = listener; |
|
55 mExtraHeaders = extraHeaders; |
|
56 mConnected = false; |
|
57 mParser = new HybiParser(this); |
|
58 |
|
59 mHandlerThread = new HandlerThread("websocket-thread"); |
|
60 mHandlerThread.start(); |
|
61 mHandler = new Handler(mHandlerThread.getLooper()); |
|
62 } |
|
63 |
|
64 public Listener getListener() { |
|
65 return mListener; |
|
66 } |
|
67 |
|
68 public void connect() { |
|
69 if (mThread != null && mThread.isAlive()) { |
|
70 return; |
|
71 } |
|
72 |
|
73 mThread = new Thread(new Runnable() { |
|
74 @Override |
|
75 public void run() { |
|
76 try { |
|
77 int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); |
|
78 |
|
79 String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); |
|
80 if (!TextUtils.isEmpty(mURI.getQuery())) { |
|
81 path += "?" + mURI.getQuery(); |
|
82 } |
|
83 |
|
84 String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; |
|
85 URI origin = new URI(originScheme, "//" + mURI.getHost(), null); |
|
86 |
|
87 SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); |
|
88 mSocket = factory.createSocket(mURI.getHost(), port); |
|
89 |
|
90 PrintWriter out = new PrintWriter(mSocket.getOutputStream()); |
|
91 out.print("GET " + path + " HTTP/1.1\r\n"); |
|
92 out.print("Upgrade: websocket\r\n"); |
|
93 out.print("Connection: Upgrade\r\n"); |
|
94 out.print("Host: " + mURI.getHost() + "\r\n"); |
|
95 out.print("Origin: " + origin.toString() + "\r\n"); |
|
96 out.print("Sec-WebSocket-Key: " + createSecret() + "\r\n"); |
|
97 out.print("Sec-WebSocket-Version: 13\r\n"); |
|
98 if (mExtraHeaders != null) { |
|
99 for (NameValuePair pair : mExtraHeaders) { |
|
100 out.print(String.format("%s: %s\r\n", pair.getName(), pair.getValue())); |
|
101 } |
|
102 } |
|
103 out.print("\r\n"); |
|
104 out.flush(); |
|
105 |
|
106 HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); |
|
107 |
|
108 // Read HTTP response status line. |
|
109 StatusLine statusLine = parseStatusLine(readLine(stream)); |
|
110 if (statusLine == null) { |
|
111 throw new HttpException("Received no reply from server."); |
|
112 } else if (statusLine.getStatusCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) { |
|
113 throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); |
|
114 } |
|
115 |
|
116 // Read HTTP response headers. |
|
117 String line; |
|
118 while (!TextUtils.isEmpty(line = readLine(stream))) { |
|
119 Header header = parseHeader(line); |
|
120 if (header.getName().equals("Sec-WebSocket-Accept")) { |
|
121 // FIXME: Verify the response... |
|
122 } |
|
123 } |
|
124 |
|
125 mListener.onConnect(); |
|
126 |
|
127 mConnected = true; |
|
128 |
|
129 // Now decode websocket frames. |
|
130 mParser.start(stream); |
|
131 |
|
132 } catch (EOFException ex) { |
|
133 Log.d(TAG, "WebSocket EOF!", ex); |
|
134 mListener.onDisconnect(0, "EOF"); |
|
135 mConnected = false; |
|
136 |
|
137 } catch (SSLException ex) { |
|
138 // Connection reset by peer |
|
139 Log.d(TAG, "Websocket SSL error!", ex); |
|
140 mListener.onDisconnect(0, "SSL"); |
|
141 mConnected = false; |
|
142 |
|
143 } catch (Exception ex) { |
|
144 mListener.onError(ex); |
|
145 } |
|
146 } |
|
147 }); |
|
148 mThread.start(); |
|
149 } |
|
150 |
|
151 public void disconnect() { |
|
152 if (mSocket != null) { |
|
153 mHandler.post(new Runnable() { |
|
154 @Override |
|
155 public void run() { |
|
156 if (mSocket != null) { |
|
157 try { |
|
158 mSocket.close(); |
|
159 } catch (IOException ex) { |
|
160 Log.d(TAG, "Error while disconnecting", ex); |
|
161 mListener.onError(ex); |
|
162 } |
|
163 mSocket = null; |
|
164 } |
|
165 mConnected = false; |
|
166 } |
|
167 }); |
|
168 } |
|
169 } |
|
170 |
|
171 public void send(String data) { |
|
172 sendFrame(mParser.frame(data)); |
|
173 } |
|
174 |
|
175 public void send(byte[] data) { |
|
176 sendFrame(mParser.frame(data)); |
|
177 } |
|
178 |
|
179 public boolean isConnected() { |
|
180 return mConnected; |
|
181 } |
|
182 |
|
183 private StatusLine parseStatusLine(String line) { |
|
184 if (TextUtils.isEmpty(line)) { |
|
185 return null; |
|
186 } |
|
187 return BasicLineParser.parseStatusLine(line, new BasicLineParser()); |
|
188 } |
|
189 |
|
190 private Header parseHeader(String line) { |
|
191 return BasicLineParser.parseHeader(line, new BasicLineParser()); |
|
192 } |
|
193 |
|
194 // Can't use BufferedReader because it buffers past the HTTP data. |
|
195 private String readLine(HybiParser.HappyDataInputStream reader) throws IOException { |
|
196 int readChar = reader.read(); |
|
197 if (readChar == -1) { |
|
198 return null; |
|
199 } |
|
200 StringBuilder string = new StringBuilder(""); |
|
201 while (readChar != '\n') { |
|
202 if (readChar != '\r') { |
|
203 string.append((char) readChar); |
|
204 } |
|
205 |
|
206 readChar = reader.read(); |
|
207 if (readChar == -1) { |
|
208 return null; |
|
209 } |
|
210 } |
|
211 return string.toString(); |
|
212 } |
|
213 |
|
214 private String createSecret() { |
|
215 byte[] nonce = new byte[16]; |
|
216 for (int i = 0; i < 16; i++) { |
|
217 nonce[i] = (byte) (Math.random() * 256); |
|
218 } |
|
219 return Base64.encodeToString(nonce, Base64.DEFAULT).trim(); |
|
220 } |
|
221 |
|
222 void sendFrame(final byte[] frame) { |
|
223 mHandler.post(new Runnable() { |
|
224 @Override |
|
225 public void run() { |
|
226 try { |
|
227 synchronized (mSendLock) { |
|
228 OutputStream outputStream = mSocket.getOutputStream(); |
|
229 outputStream.write(frame); |
|
230 outputStream.flush(); |
|
231 } |
|
232 } catch (IOException e) { |
|
233 mListener.onError(e); |
|
234 } |
|
235 } |
|
236 }); |
|
237 } |
|
238 |
|
239 public interface Listener { |
|
240 public void onConnect(); |
|
241 public void onMessage(String message); |
|
242 public void onMessage(byte[] data); |
|
243 public void onDisconnect(int code, String reason); |
|
244 public void onError(Exception error); |
|
245 } |
|
246 |
|
247 private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { |
|
248 SSLContext context = SSLContext.getInstance("TLS"); |
|
249 context.init(null, sTrustManagers, null); |
|
250 return context.getSocketFactory(); |
|
251 } |
|
252 } |