package com.koushikdutta.async.http;
import java.nio.ByteBuffer;
import java.nio.LongBuffer;
import java.security.MessageDigest;
import java.util.LinkedList;
import java.util.UUID;
import com.hupu.games.common.Base64;
import com.koushikdutta.async.AsyncServer;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.BufferedDataSink;
import com.koushikdutta.async.ByteBufferList;
import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.callback.WritableCallback;
import com.koushikdutta.async.http.libcore.RawHeaders;
import com.koushikdutta.async.http.server.AsyncHttpServerRequest;
import com.koushikdutta.async.http.server.AsyncHttpServerResponse;
public class WebSocketImpl implements WebSocket {
@Override
public void end() {
mSocket.end();
}
private static byte[] toByteArray(UUID uuid) {
byte[] byteArray = new byte[(Long.SIZE / Byte.SIZE) * 2];
ByteBuffer buffer = ByteBuffer.wrap(byteArray);
LongBuffer longBuffer = buffer.asLongBuffer();
longBuffer.put(new long[] { uuid.getMostSignificantBits(),uuid.getLeastSignificantBits() });
return byteArray;
}
private static String SHA1(String text) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(text.getBytes("iso-8859-1"), 0, text.length());
byte[] sha1hash = md.digest();
return Base64.encodeToString(sha1hash, 0);
}
catch (Exception ex) {
return null;
}
}
final static String MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private LinkedList<ByteBufferList> pending;
private void addAndEmit(ByteBufferList bb) {
if (pending == null) {
Util.emitAllData(this, bb);
if (bb.remaining() > 0) {
pending = new LinkedList<ByteBufferList>();
pending.add(bb);
}
return;
}
while (!isPaused()) {
bb = pending.remove();
Util.emitAllData(this, bb);
if (bb.remaining() > 0)
pending.add(0, bb);
}
if (pending.size() == 0)
pending = null;
}
private void setupParser(boolean masking) {
mParser = new HybiParser(mSocket) {
@Override
protected void report(Exception ex) {
if (WebSocketImpl.this.mExceptionCallback != null)
WebSocketImpl.this.mExceptionCallback.onCompleted(ex);
}
@Override
protected void onMessage(byte[] payload) {
addAndEmit(new ByteBufferList(payload));
}
@Override
protected void onMessage(String payload) {
if (WebSocketImpl.this.mStringCallback != null)
WebSocketImpl.this.mStringCallback.onStringAvailable(payload);
}
@Override
protected void onDisconnect(int code, String reason) {
mSocket.close();
// if (WebSocketImpl.this.mClosedCallback != null)
// WebSocketImpl.this.mClosedCallback.onCompleted(null);
}
@Override
protected void sendFrame(byte[] frame) {
mSink.write(ByteBuffer.wrap(frame));
}
};
mParser.setMasking(masking);
if (mSocket.isPaused())
mSocket.resume();
}
private AsyncSocket mSocket;
BufferedDataSink mSink;
public WebSocketImpl(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
this(request.getSocket());
String key = request.getHeaders().getHeaders().get("Sec-WebSocket-Key");
String concat = key + MAGIC;
String sha1 = SHA1(concat);
String origin = request.getHeaders().getHeaders().get("Origin");
response.responseCode(101);
response.getHeaders().getHeaders().set("Upgrade", "WebSocket");
response.getHeaders().getHeaders().set("Connection", "Upgrade");
response.getHeaders().getHeaders().set("Sec-WebSocket-Accept", sha1);
// if (origin != null)
// response.getHeaders().getHeaders().set("Access-Control-Allow-Origin", "http://" + origin);
response.writeHead();
setupParser(false);
}
public static void addWebSocketUpgradeHeaders(AsyncHttpRequest req, String protocol) {
RawHeaders headers = req.getHeaders().getHeaders();
final String key = Base64.encodeToString(toByteArray(UUID.randomUUID()),Base64.NO_WRAP);
headers.set("Sec-WebSocket-Version", "13");
headers.set("Sec-WebSocket-Key", key);
headers.set("Connection", "Upgrade");
headers.set("Upgrade", "websocket");
if (protocol != null)
headers.set("Sec-WebSocket-Protocol", protocol);
headers.set("Pragma", "no-cache");
headers.set("Cache-Control", "no-cache");
req.getHeaders().setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.15 Safari/537.36");
}
public WebSocketImpl(AsyncSocket socket) {
mSocket = socket;
mSink = new BufferedDataSink(mSocket);
}
public static WebSocket finishHandshake(RawHeaders requestHeaders, AsyncHttpResponse response) {
if (response == null)
return null;
if (response.getHeaders().getHeaders().getResponseCode() != 101)
return null;
if (!"websocket".equalsIgnoreCase(response.getHeaders().getHeaders().get("Upgrade")))
return null;
String sha1 = response.getHeaders().getHeaders().get("Sec-WebSocket-Accept");
if (sha1 == null)
return null;
String key = requestHeaders.get("Sec-WebSocket-Key");
if (key == null)
return null;
String concat = key + MAGIC;
String expected = SHA1(concat).trim();
if (!sha1.equalsIgnoreCase(expected))
return null;
WebSocketImpl ret = new WebSocketImpl(response.detachSocket());
ret.setupParser(true);
return ret;
}
HybiParser mParser;
@Override
public void close() {
mSocket.close();
}
@Override
public void setClosedCallback(CompletedCallback handler) {
mSocket.setClosedCallback(handler);
}
@Override
public CompletedCallback getClosedCallback() {
return mSocket.getClosedCallback();
}
CompletedCallback mExceptionCallback;
@Override
public void setEndCallback(CompletedCallback callback) {
mExceptionCallback = callback;
}
@Override
public CompletedCallback getEndCallback() {
return mExceptionCallback;
}
@Override
public void send(byte[] bytes) {
mSink.write(ByteBuffer.wrap(mParser.frame(bytes)));
}
@Override
public void send(String string) {
mSink.write(ByteBuffer.wrap(mParser.frame(string)));
}
private StringCallback mStringCallback;
@Override
public void setStringCallback(StringCallback callback) {
mStringCallback = callback;
}
private DataCallback mDataCallback;
@Override
public void setDataCallback(DataCallback callback) {
mDataCallback = callback;
}
@Override
public StringCallback getStringCallback() {
return mStringCallback;
}
@Override
public DataCallback getDataCallback() {
return mDataCallback;
}
@Override
public boolean isOpen() {
return mSocket.isOpen();
}
@Override
public boolean isBuffering() {
return mSink.remaining() > 0;
}
@Override
public void write(ByteBuffer bb) {
byte[] buf = new byte[bb.remaining()];
bb.get(buf);
bb.position(0);
bb.limit(0);
send(buf);
}
@Override
public void write(ByteBufferList bb) {
byte[] buf = bb.getAllByteArray();
send(buf);
}
@Override
public void setWriteableCallback(WritableCallback handler) {
mSink.setWriteableCallback(handler);
}
@Override
public WritableCallback getWriteableCallback() {
return mSink.getWriteableCallback();
}
@Override
public AsyncSocket getSocket() {
return mSocket;
}
@Override
public AsyncServer getServer() {
return mSocket.getServer();
}
@Override
public boolean isChunked() {
return false;
}
@Override
public void pause() {
mSocket.pause();
}
@Override
public void resume() {
mSocket.resume();
}
@Override
public boolean isPaused() {
return mSocket.isPaused();
}
}