Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 // This file was copied from:
2 // https://github.com/koush/android-websockets/blob/master/src/com/codebutler/android_websockets/WebSocketClient.java
4 package com.codebutler.android_websockets;
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;
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;
31 public class WebSocketClient {
32 private static final String TAG = "WebSocketClient";
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;
44 private final Object mSendLock = new Object();
46 private static TrustManager[] sTrustManagers;
48 public static void setTrustManagers(TrustManager[] tm) {
49 sTrustManagers = tm;
50 }
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);
59 mHandlerThread = new HandlerThread("websocket-thread");
60 mHandlerThread.start();
61 mHandler = new Handler(mHandlerThread.getLooper());
62 }
64 public Listener getListener() {
65 return mListener;
66 }
68 public void connect() {
69 if (mThread != null && mThread.isAlive()) {
70 return;
71 }
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);
79 String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath();
80 if (!TextUtils.isEmpty(mURI.getQuery())) {
81 path += "?" + mURI.getQuery();
82 }
84 String originScheme = mURI.getScheme().equals("wss") ? "https" : "http";
85 URI origin = new URI(originScheme, "//" + mURI.getHost(), null);
87 SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault();
88 mSocket = factory.createSocket(mURI.getHost(), port);
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();
106 HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream());
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 }
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 }
125 mListener.onConnect();
127 mConnected = true;
129 // Now decode websocket frames.
130 mParser.start(stream);
132 } catch (EOFException ex) {
133 Log.d(TAG, "WebSocket EOF!", ex);
134 mListener.onDisconnect(0, "EOF");
135 mConnected = false;
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;
143 } catch (Exception ex) {
144 mListener.onError(ex);
145 }
146 }
147 });
148 mThread.start();
149 }
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 }
171 public void send(String data) {
172 sendFrame(mParser.frame(data));
173 }
175 public void send(byte[] data) {
176 sendFrame(mParser.frame(data));
177 }
179 public boolean isConnected() {
180 return mConnected;
181 }
183 private StatusLine parseStatusLine(String line) {
184 if (TextUtils.isEmpty(line)) {
185 return null;
186 }
187 return BasicLineParser.parseStatusLine(line, new BasicLineParser());
188 }
190 private Header parseHeader(String line) {
191 return BasicLineParser.parseHeader(line, new BasicLineParser());
192 }
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 }
206 readChar = reader.read();
207 if (readChar == -1) {
208 return null;
209 }
210 }
211 return string.toString();
212 }
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 }
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 }
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 }
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 }