support protocol ver 3 and sending byte data

This commit is contained in:
Naoyuki Kanezawa
2014-03-30 18:46:31 +09:00
parent 808ad8c4f1
commit 19c820334d
9 changed files with 456 additions and 117 deletions

View File

@@ -13,7 +13,10 @@ import com.google.gson.Gson;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -298,7 +301,7 @@ public abstract class Socket extends Emitter {
if (failed[0]) return; if (failed[0]) return;
logger.fine(String.format("probe transport '%s' opened", name)); logger.fine(String.format("probe transport '%s' opened", name));
Packet packet = new Packet(Packet.PING, "probe"); Packet<String> packet = new Packet<String>(Packet.PING, "probe");
transport[0].send(new Packet[] {packet}); transport[0].send(new Packet[] {packet});
transport[0].once(Transport.EVENT_PACKET, new Listener() { transport[0].once(Transport.EVENT_PACKET, new Listener() {
@Override @Override
@@ -395,7 +398,7 @@ public abstract class Socket extends Emitter {
this.emit(EVENT_HEARTBEAT); this.emit(EVENT_HEARTBEAT);
if (Packet.OPEN.equals(packet.type)) { if (Packet.OPEN.equals(packet.type)) {
this.onHandshake(gson.fromJson(packet.data, HandshakeData.class)); this.onHandshake(gson.fromJson((String)packet.data, HandshakeData.class));
} else if (Packet.PONG.equals(packet.type)) { } else if (Packet.PONG.equals(packet.type)) {
this.setPing(); this.setPing();
} else if (Packet.ERROR.equals(packet.type)) { } else if (Packet.ERROR.equals(packet.type)) {
@@ -406,7 +409,11 @@ public abstract class Socket extends Emitter {
} else if (Packet.MESSAGE.equals(packet.type)) { } else if (Packet.MESSAGE.equals(packet.type)) {
this.emit(EVENT_DATA, packet.data); this.emit(EVENT_DATA, packet.data);
this.emit(EVENT_MESSAGE, packet.data); this.emit(EVENT_MESSAGE, packet.data);
this.onmessage(packet.data); if (packet.data instanceof String) {
this.onmessage((String)packet.data);
} else if (packet.data instanceof byte[]) {
this.onmessage((byte[])packet.data);
}
} }
} else { } else {
logger.fine(String.format("packet received with socket readyState '%s'", this.readyState)); logger.fine(String.format("packet received with socket readyState '%s'", this.readyState));
@@ -530,6 +537,14 @@ public abstract class Socket extends Emitter {
this.send(msg, fn); this.send(msg, fn);
} }
public void write(byte[] msg) {
this.write(msg, null);
}
public void write(byte[] msg, Runnable fn) {
this.send(msg, fn);
}
/** /**
* Sends a message. * Sends a message.
* *
@@ -539,6 +554,10 @@ public abstract class Socket extends Emitter {
this.send(msg, null); this.send(msg, null);
} }
public void send(byte[] msg) {
this.send(msg, null);
}
/** /**
* Sends a message. * Sends a message.
* *
@@ -554,17 +573,35 @@ public abstract class Socket extends Emitter {
}); });
} }
public void send(final byte[] msg, final Runnable fn) {
EventThread.exec(new Runnable() {
@Override
public void run() {
Socket.this.sendPacket(Packet.MESSAGE, msg, fn);
}
});
}
private void sendPacket(String type) { private void sendPacket(String type) {
this.sendPacket(type, null, null); this.sendPacket(new Packet(type), null);
} }
private void sendPacket(String type, String data, Runnable fn) { private void sendPacket(String type, String data, Runnable fn) {
Packet<String> packet = new Packet<String>(type, data);
sendPacket(packet, fn);
}
private void sendPacket(String type, byte[] data, Runnable fn) {
Packet<byte[]> packet = new Packet<byte[]>(type, data);
sendPacket(packet, fn);
}
private void sendPacket(Packet packet, Runnable fn) {
if (fn == null) { if (fn == null) {
// ConcurrentLinkedList does not permit `null`. // ConcurrentLinkedList does not permit `null`.
fn = noop; fn = noop;
} }
Packet packet = new Packet(type, data);
this.emit(EVENT_PACKET_CREATE, packet); this.emit(EVENT_PACKET_CREATE, packet);
this.writeBuffer.offer(packet); this.writeBuffer.offer(packet);
this.callbackBuffer.offer(fn); this.callbackBuffer.offer(fn);
@@ -653,6 +690,8 @@ public abstract class Socket extends Emitter {
return filteredUpgrades; return filteredUpgrades;
} }
public void onmessage(byte[] data) {};
public abstract void onopen(); public abstract void onopen();
public abstract void onmessage(String data); public abstract void onmessage(String data);

View File

@@ -108,6 +108,10 @@ public abstract class Transport extends Emitter {
this.onPacket(Parser.decodePacket(data)); this.onPacket(Parser.decodePacket(data));
} }
protected void onData(byte[] data) {
this.onPacket(Parser.decodePacket(data));
}
protected void onPacket(Packet packet) { protected void onPacket(Packet packet) {
this.emit(EVENT_PACKET, packet); this.emit(EVENT_PACKET, packet);
} }

View File

@@ -1,11 +1,11 @@
package com.github.nkzawa.engineio.client.transports; package com.github.nkzawa.engineio.client.transports;
import com.github.nkzawa.thread.EventThread;
import com.github.nkzawa.engineio.client.Transport; import com.github.nkzawa.engineio.client.Transport;
import com.github.nkzawa.engineio.client.Util; import com.github.nkzawa.engineio.client.Util;
import com.github.nkzawa.engineio.parser.Packet; import com.github.nkzawa.engineio.parser.Packet;
import com.github.nkzawa.engineio.parser.Parser; import com.github.nkzawa.engineio.parser.Parser;
import com.github.nkzawa.thread.EventThread;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@@ -94,11 +94,20 @@ abstract public class Polling extends Transport {
this.emit(EVENT_POLL); this.emit(EVENT_POLL);
} }
@Override
protected void onData(String data) { protected void onData(String data) {
_onData(data);
}
@Override
protected void onData(byte[] data) {
_onData(data);
}
private void _onData(Object data) {
final Polling self = this; final Polling self = this;
logger.fine(String.format("polling got data %s", data)); logger.fine(String.format("polling got data %s", data));
Parser.DecodePayloadCallback callback = new Parser.DecodePayloadCallback() {
Parser.decodePayload(data, new Parser.DecodePayloadCallback() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet packet, int index, int total) {
if (self.readyState == ReadyState.OPENING) { if (self.readyState == ReadyState.OPENING) {
@@ -113,7 +122,13 @@ abstract public class Polling extends Transport {
self.onPacket(packet); self.onPacket(packet);
return true; return true;
} }
}); };
if (data instanceof String) {
Parser.decodePayload((String)data, callback);
} else if (data instanceof byte[]) {
Parser.decodePayload((byte[])data, callback);
}
if (this.readyState != ReadyState.CLOSED) { if (this.readyState != ReadyState.CLOSED) {
this.polling = false; this.polling = false;
@@ -134,7 +149,7 @@ abstract public class Polling extends Transport {
@Override @Override
public void call(Object... args) { public void call(Object... args) {
logger.fine("writing close packet"); logger.fine("writing close packet");
self.write(new Packet[] {new Packet(Packet.CLOSE, null)}); self.write(new Packet[] {new Packet(Packet.CLOSE)});
} }
}; };
@@ -152,12 +167,19 @@ abstract public class Polling extends Transport {
protected void write(Packet[] packets) { protected void write(Packet[] packets) {
final Polling self = this; final Polling self = this;
this.writable = false; this.writable = false;
this.doWrite(Parser.encodePayload(packets), new Runnable() { final Runnable callbackfn = new Runnable() {
@Override @Override
public void run() { public void run() {
self.writable = true; self.writable = true;
self.emit(EVENT_DRAIN); self.emit(EVENT_DRAIN);
} }
};
Parser.encodePayload(packets, new Parser.EncodeCallback<byte[]>() {
@Override
public void call(byte[] data) {
self.doWrite(data, callbackfn);
}
}); });
} }
@@ -187,7 +209,7 @@ abstract public class Polling extends Transport {
return schema + "://" + this.hostname + port + this.path + _query; return schema + "://" + this.hostname + port + this.path + _query;
} }
abstract protected void doWrite(String data, Runnable fn); abstract protected void doWrite(byte[] data, Runnable fn);
abstract protected void doPoll(); abstract protected void doPoll();
} }

View File

@@ -7,6 +7,9 @@ import com.github.nkzawa.thread.EventThread;
import java.io.*; import java.io.*;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -57,7 +60,8 @@ public class PollingXHR extends Polling {
return req; return req;
} }
protected void doWrite(String data, final Runnable fn) { @Override
protected void doWrite(byte[] data, final Runnable fn) {
Request.Options opts = new Request.Options(); Request.Options opts = new Request.Options();
opts.method = "POST"; opts.method = "POST";
opts.data = data; opts.data = data;
@@ -90,6 +94,7 @@ public class PollingXHR extends Polling {
this.sendXhr = req; this.sendXhr = req;
} }
@Override
protected void doPoll() { protected void doPoll() {
logger.fine("xhr poll"); logger.fine("xhr poll");
Request req = this.request(); Request req = this.request();
@@ -100,8 +105,12 @@ public class PollingXHR extends Polling {
EventThread.exec(new Runnable() { EventThread.exec(new Runnable() {
@Override @Override
public void run() { public void run() {
String data = args.length > 0 ? (String) args[0] : null; Object arg = args.length > 0 ? args[0] : null;
self.onData(data); if (arg instanceof String) {
self.onData((String)arg);
} else if (arg instanceof byte[]) {
self.onData((byte[])arg);
}
} }
}); });
} }
@@ -134,7 +143,7 @@ public class PollingXHR extends Polling {
String method; String method;
String uri; String uri;
String data; byte[] data;
HttpURLConnection xhr; HttpURLConnection xhr;
public Request(Options opts) { public Request(Options opts) {
@@ -159,7 +168,7 @@ public class PollingXHR extends Polling {
if ("POST".equals(this.method)) { if ("POST".equals(this.method)) {
xhr.setDoOutput(true); xhr.setDoOutput(true);
headers.put("Content-type", "text/plain;charset=UTF-8"); headers.put("Content-type", "application/octet-stream");
} }
self.onRequestHeaders(headers); self.onRequestHeaders(headers);
@@ -171,15 +180,15 @@ public class PollingXHR extends Polling {
xhrService.submit(new Runnable() { xhrService.submit(new Runnable() {
@Override @Override
public void run() { public void run() {
BufferedWriter writer = null; OutputStream output = null;
InputStream input = null;
BufferedReader reader = null; BufferedReader reader = null;
try { try {
if (self.data != null) { if (self.data != null) {
byte[] data = self.data.getBytes("UTF-8"); xhr.setFixedLengthStreamingMode(self.data.length);
xhr.setFixedLengthStreamingMode(data.length); output = new BufferedOutputStream(xhr.getOutputStream());
writer = new BufferedWriter(new OutputStreamWriter(xhr.getOutputStream())); output.write(self.data);
writer.write(self.data); output.flush();
writer.flush();
} }
Map<String, String> headers = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); Map<String, String> headers = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
@@ -189,28 +198,46 @@ public class PollingXHR extends Polling {
} }
self.onResponseHeaders(headers); self.onResponseHeaders(headers);
StringBuilder data = null;
final int statusCode = xhr.getResponseCode(); final int statusCode = xhr.getResponseCode();
if (HttpURLConnection.HTTP_OK == statusCode) { if (HttpURLConnection.HTTP_OK == statusCode) {
String line; String contentType = xhr.getContentType();
data = new StringBuilder(); if ("application/octet-stream".equalsIgnoreCase(contentType)) {
reader = new BufferedReader(new InputStreamReader(xhr.getInputStream())); input = new BufferedInputStream(xhr.getInputStream());
while ((line = reader.readLine()) != null) { List<byte[]> buffers = new ArrayList<byte[]>();
data.append(line); int capacity = 0;
int len = 0;
byte[] buffer = new byte[1024];
while ((len = input.read(buffer)) > 0) {
byte[] _buffer = new byte[len];
System.arraycopy(buffer, 0, _buffer, 0, len);
buffers.add(_buffer);
capacity += len;
}
ByteBuffer data = ByteBuffer.allocate(capacity);
for (byte[] b : buffers) {
data.put(b);
}
self.onData(data.array());
} else {
String line;
StringBuilder data = new StringBuilder();
reader = new BufferedReader(new InputStreamReader(xhr.getInputStream()));
while ((line = reader.readLine()) != null) {
data.append(line);
}
self.onData(data.toString());
} }
} else { } else {
self.onError(new IOException(Integer.toString(statusCode))); self.onError(new IOException(Integer.toString(statusCode)));
} }
if (data != null) {
self.onData(data.toString());
}
} catch (IOException e) { } catch (IOException e) {
self.onError(e); self.onError(e);
} finally { } finally {
try { try {
if (writer != null) writer.close(); if (output != null) output.close();
} catch (IOException e) {}
try {
if (input != null) input.close();
} catch (IOException e) {} } catch (IOException e) {}
try { try {
if (reader != null) reader.close(); if (reader != null) reader.close();
@@ -230,6 +257,11 @@ public class PollingXHR extends Polling {
this.onSuccess(); this.onSuccess();
} }
private void onData(byte[] data) {
this.emit(EVENT_DATA, data);
this.onSuccess();
}
private void onError(Exception err) { private void onError(Exception err) {
this.emit(EVENT_ERROR, err); this.emit(EVENT_ERROR, err);
this.cleanup(); this.cleanup();
@@ -260,7 +292,7 @@ public class PollingXHR extends Polling {
public String uri; public String uri;
public String method; public String method;
public String data; public byte[] data;
} }
} }
} }

View File

@@ -12,6 +12,7 @@ import org.java_websocket.handshake.ServerHandshake;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@@ -78,6 +79,15 @@ public class WebSocket extends Transport {
}); });
} }
@Override @Override
public void onMessage(final ByteBuffer s) {
EventThread.exec(new Runnable() {
@Override
public void run() {
self.onData(s.array());
}
});
}
@Override
public void onError(final Exception e) { public void onError(final Exception e) {
EventThread.exec(new Runnable() { EventThread.exec(new Runnable() {
@Override @Override
@@ -97,7 +107,16 @@ public class WebSocket extends Transport {
final WebSocket self = this; final WebSocket self = this;
this.writable = false; this.writable = false;
for (Packet packet : packets) { for (Packet packet : packets) {
this.ws.send(Parser.encodePacket(packet)); Parser.encodePacket(packet, new Parser.EncodeCallback() {
@Override
public void call(Object packet) {
if (packet instanceof String) {
self.ws.send((String) packet);
} else if (packet instanceof byte[]) {
self.ws.send((byte[]) packet);
}
}
});
} }
final Runnable ondrain = new Runnable() { final Runnable ondrain = new Runnable() {

View File

@@ -1,7 +1,7 @@
package com.github.nkzawa.engineio.parser; package com.github.nkzawa.engineio.parser;
public class Packet { public class Packet<T> {
static final public String OPEN = "open"; static final public String OPEN = "open";
static final public String CLOSE = "close"; static final public String CLOSE = "close";
@@ -13,13 +13,14 @@ public class Packet {
static final public String ERROR = "error"; static final public String ERROR = "error";
public String type; public String type;
public String data; public T data;
public Packet(String type) { public Packet(String type) {
this(type, null); this(type, null);
} }
public Packet(String type, String data) { public Packet(String type, T data) {
this.type = type; this.type = type;
this.data = data; this.data = data;
} }

View File

@@ -1,12 +1,17 @@
package com.github.nkzawa.engineio.parser; package com.github.nkzawa.engineio.parser;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public class Parser { public class Parser {
public static final int protocol = 2; public static final int protocol = 3;
private static final Map<String, Integer> packets = new HashMap<String, Integer>() {{ private static final Map<String, Integer> packets = new HashMap<String, Integer>() {{
put(Packet.OPEN, 0); put(Packet.OPEN, 0);
put(Packet.CLOSE, 1); put(Packet.CLOSE, 1);
@@ -16,54 +21,116 @@ public class Parser {
put(Packet.UPGRADE, 5); put(Packet.UPGRADE, 5);
put(Packet.NOOP, 6); put(Packet.NOOP, 6);
}}; }};
private static final Map<Integer, String> bipackets = new HashMap<Integer, String>();
private static final Map<Integer, String> packetslist = new HashMap<Integer, String>();
static { static {
for (Map.Entry<String, Integer> entry : packets.entrySet()) { for (Map.Entry<String, Integer> entry : packets.entrySet()) {
bipackets.put(entry.getValue(), entry.getKey()); packetslist.put(entry.getValue(), entry.getKey());
} }
} }
private static Packet err = new Packet(Packet.ERROR, "parser error"); private static Packet<String> err = new Packet<String>(Packet.ERROR, "parser error");
private Parser() {} private Parser() {}
public static String encodePacket(Packet packet) { public static void encodePacket(Packet packet, EncodeCallback callback) {
if (packet.data instanceof byte[]) {
@SuppressWarnings("unchecked")
Packet<byte[]> _packet = packet;
@SuppressWarnings("unchecked")
EncodeCallback<byte[]> _callback = callback;
encodeByteArray(_packet, _callback);
return;
}
String encoded = String.valueOf(packets.get(packet.type)); String encoded = String.valueOf(packets.get(packet.type));
if (packet.data != null) { if (null != packet.data) {
encoded += packet.data; encoded += packet.data;
} }
return encoded;
@SuppressWarnings("unchecked")
EncodeCallback<String> _callback = callback;
_callback.call(encoded);
} }
public static Packet decodePacket(String data) { private static void encodeByteArray(Packet<byte[]> packet, EncodeCallback<byte[]> callback) {
int type = -1; byte[] data = packet.data;
byte[] resultArray = new byte[1 + data.length];
resultArray[0] = packets.get(packet.type).byteValue();
System.arraycopy(data, 0, resultArray, 1, data.length);
callback.call(resultArray);
}
public static Packet<String> decodePacket(String data) {
int type;
try { try {
type = Character.getNumericValue(data.charAt(0)); type = Character.getNumericValue(data.charAt(0));
} catch(IndexOutOfBoundsException e) {} } catch (IndexOutOfBoundsException e) {
if (type < 0 || type >= packets.size()) { type = -1;
}
if (type < 0 || type >= packetslist.size()) {
return err; return err;
} }
return new Packet(bipackets.get(type), data.length() > 1 ? data.substring(1) : null); if (data.length() > 1) {
return new Packet<String>(packetslist.get(type), data.substring(1));
} else {
return new Packet<String>(packetslist.get(type));
}
} }
public static String encodePayload(Packet[] packets) { public static Packet<byte[]> decodePacket(byte[] data) {
int type = data[0];
byte[] intArray = new byte[data.length - 1];
System.arraycopy(data, 1, intArray, 0, intArray.length);
return new Packet<byte[]>(packetslist.get(type), intArray);
}
public static void encodePayload(Packet[] packets, EncodeCallback<byte[]> callback) {
if (packets.length == 0) { if (packets.length == 0) {
return "0:"; callback.call(new byte[0]);
return;
} }
StringBuilder encoded = new StringBuilder(); final ArrayList<ByteBuffer> results = new ArrayList<ByteBuffer>(packets.length);
for (Packet packet : packets) { for (Packet packet : packets) {
String message = encodePacket(packet); encodePacket(packet, new EncodeCallback() {
encoded.append(message.length()).append(":").append(message); @Override
public void call(Object packet) {
if (packet instanceof String) {
String encodingLength = String.valueOf(((String)packet).getBytes(Charset.forName("UTF-8")).length);
ByteBuffer sizeBuffer = ByteBuffer.allocate(encodingLength.length() + 2);
sizeBuffer.put((byte)0); // is a string
for (char ch : encodingLength.toCharArray()) {
sizeBuffer.put((byte)Character.getNumericValue(ch));
}
sizeBuffer.put((byte)255);
results.add(Buffer.concat(new ByteBuffer[] {sizeBuffer,
ByteBuffer.wrap(((String)packet).getBytes(Charset.forName("UTF-8")))}));
return;
}
String encodingLength = String.valueOf(((ByteBuffer)packet).capacity());
ByteBuffer sizeBuffer = ByteBuffer.allocate(encodingLength.length() + 2);
sizeBuffer.put((byte)1); // is binary
for (char ch : encodingLength.toCharArray()) {
sizeBuffer.put((byte)ch);
}
sizeBuffer.put((byte)255);
results.add(Buffer.concat(new ByteBuffer[] {sizeBuffer, (ByteBuffer)packet}));
}
});
} }
return encoded.toString(); callback.call(Buffer.concat(results.toArray(new ByteBuffer[results.size()])).array());
} }
public static void decodePayload(String data, DecodePayloadCallback callback) { public static void decodePayload(String data, DecodePayloadCallback<String> callback) {
if (data == null || data.isEmpty()) { if (data == null || data.isEmpty()) {
callback.call(err, 0, 1); callback.call(err, 0, 1);
return; return;
@@ -93,7 +160,8 @@ public class Parser {
} }
if (msg.length() != 0) { if (msg.length() != 0) {
Packet packet = decodePacket(msg); Packet<String> packet = decodePacket(msg);
if (err.type.equals(packet.type) && err.data.equals(packet.data)) { if (err.type.equals(packet.type) && err.data.equals(packet.data)) {
callback.call(err, 0, 1); callback.call(err, 0, 1);
return; return;
@@ -113,8 +181,91 @@ public class Parser {
} }
} }
public static interface DecodePayloadCallback { public static void decodePayload(byte[] data, DecodePayloadCallback callback) {
ByteBuffer bufferTail = ByteBuffer.wrap(data);
List<Object> buffers = new ArrayList<Object>();
public boolean call(Packet packet, int index, int total); while (bufferTail.capacity() > 0) {
StringBuilder strLen = new StringBuilder();
boolean isString = (bufferTail.get(0) & 0xFF) == 0;
for (int i = 1; ; i++) {
int b = bufferTail.get(i) & 0xFF;
if (b == 255) break;
strLen.append(b);
}
bufferTail.position(strLen.length() + 1);
bufferTail = bufferTail.slice();
int msgLength = Integer.parseInt(strLen.toString());
bufferTail.position(1);
bufferTail.limit(msgLength + 1);
byte[] msg = new byte[bufferTail.remaining()];
bufferTail.get(msg);
if (isString) {
buffers.add(new String(msg, Charset.forName("UTF-8")));
} else {
buffers.add(msg);
}
bufferTail.clear();
bufferTail.position(msgLength + 1);
bufferTail = bufferTail.slice();
}
int total = buffers.size();
for (int i = 0; i < total; i++) {
Object buffer = buffers.get(i);
if (buffer instanceof String) {
@SuppressWarnings("unchecked")
DecodePayloadCallback<String> _callback = callback;
_callback.call(decodePacket((String)buffer), i, total);
} else if (buffer instanceof byte[]) {
@SuppressWarnings("unchecked")
DecodePayloadCallback<byte[]> _callback = callback;
_callback.call(decodePacket((byte[])buffer), i, total);
}
}
}
public static interface EncodeCallback<T> {
public void call(T data);
}
public static interface DecodePayloadCallback<T> {
public boolean call(Packet<T> packet, int index, int total);
}
}
class Buffer {
private Buffer() {}
public static ByteBuffer concat(ByteBuffer[] list) {
int length = 0;
for (ByteBuffer buf : list) {
length += buf.capacity();
}
return concat(list, length);
}
public static ByteBuffer concat(ByteBuffer[] list, int length) {
if (list.length == 0) {
return ByteBuffer.allocate(0);
} else if (list.length == 1) {
return list[0];
}
ByteBuffer buffer = ByteBuffer.allocate(length);
for (ByteBuffer buf : list) {
buf.clear();
buffer.put(buf);
}
return buffer;
} }
} }

View File

@@ -15,91 +15,152 @@ public class ParserTest {
@Test @Test
public void encodeAsString() { public void encodeAsString() {
assertThat(encodePacket(new Packet(Packet.MESSAGE, "test")), isA(String.class)); encodePacket(new Packet<String>(Packet.MESSAGE, "test"), new EncodeCallback<String>() {
@Override
public void call(String data) {
assertThat(data, isA(String.class));
}
});
} }
@Test @Test
public void decodeAsPacket() { public void decodeAsPacket() {
assertThat(decodePacket(encodePacket(new Packet(Packet.MESSAGE, "test"))), isA(Packet.class)); encodePacket(new Packet<String>(Packet.MESSAGE, "test"), new EncodeCallback<String>() {
@Override
public void call(String data) {
assertThat(decodePacket(data), isA(Packet.class));
}
});
} }
@Test @Test
public void noData() { public void noData() {
Packet p = decodePacket(encodePacket(new Packet(Packet.MESSAGE))); encodePacket(new Packet(Packet.MESSAGE), new EncodeCallback<String>() {
assertThat(p.type, is(Packet.MESSAGE)); @Override
assertThat(p.data, is(nullValue())); public void call(String data) {
Packet p = decodePacket(data);
assertThat(p.type, is(Packet.MESSAGE));
assertThat(p.data, is(nullValue()));
}
});
} }
@Test @Test
public void encodeOpenPacket() { public void encodeOpenPacket() {
Packet p = decodePacket(encodePacket(new Packet(Packet.OPEN, "{\"some\":\"json\"}"))); encodePacket(new Packet<String>(Packet.OPEN, "{\"some\":\"json\"}"), new EncodeCallback<String>() {
assertThat(p.type, is(Packet.OPEN)); @Override
assertThat(p.data, is("{\"some\":\"json\"}")); public void call(String data) {
Packet<String> p = decodePacket(data);
assertThat(p.type, is(Packet.OPEN));
assertThat(p.data, is("{\"some\":\"json\"}"));
}
});
} }
@Test @Test
public void encodeClosePacket() { public void encodeClosePacket() {
Packet p = decodePacket(encodePacket(new Packet(Packet.CLOSE))); encodePacket(new Packet<String>(Packet.CLOSE), new EncodeCallback<String>() {
assertThat(p.type, is(Packet.CLOSE)); @Override
public void call(String data) {
Packet p = decodePacket(data);
assertThat(p.type, is(Packet.CLOSE));
}
});
} }
@Test @Test
public void encodePingPacket() { public void encodePingPacket() {
Packet p = decodePacket(encodePacket(new Packet(Packet.PING, "1"))); encodePacket(new Packet<String>(Packet.PING, "1"), new EncodeCallback<String>() {
assertThat(p.type, is(Packet.PING)); @Override
assertThat(p.data, is("1")); public void call(String data) {
Packet<String> p = decodePacket(data);
assertThat(p.type, is(Packet.PING));
assertThat(p.data, is("1"));
}
});
} }
@Test @Test
public void encodePongPacket() { public void encodePongPacket() {
Packet p = decodePacket(encodePacket(new Packet(Packet.PONG, "1"))); encodePacket(new Packet<String>(Packet.PONG, "1"), new EncodeCallback<String>() {
assertThat(p.type, is(Packet.PONG)); @Override
assertThat(p.data, is("1")); public void call(String data) {
Packet<String> p = decodePacket(data);
assertThat(p.type, is(Packet.PONG));
assertThat(p.data, is("1"));
}
});
} }
@Test @Test
public void encodeMessagePacket() { public void encodeMessagePacket() {
Packet p = decodePacket(encodePacket(new Packet(Packet.MESSAGE, "aaa"))); encodePacket(new Packet<String>(Packet.MESSAGE, "aaa"), new EncodeCallback<String>() {
assertThat(p.type, is(Packet.MESSAGE)); @Override
assertThat(p.data, is("aaa")); public void call(String data) {
Packet<String> p = decodePacket(data);
assertThat(p.type, is(Packet.MESSAGE));
assertThat(p.data, is("aaa"));
}
});
} }
@Test @Test
public void encodeUpgradePacket() { public void encodeUpgradePacket() {
Packet p = decodePacket(encodePacket(new Packet(Packet.UPGRADE))); encodePacket(new Packet<String>(Packet.UPGRADE), new EncodeCallback<String>() {
assertThat(p.type, is(Packet.UPGRADE)); @Override
public void call(String data) {
Packet p = decodePacket(data);
assertThat(p.type, is(Packet.UPGRADE));
}
});
} }
@Test @Test
public void encodingFormat() { public void encodingFormat() {
assertThat(encodePacket(new Packet(Packet.MESSAGE, "test")).matches("[0-9].*"), is(true)); encodePacket(new Packet<String>(Packet.MESSAGE, "test"), new EncodeCallback<String>() {
assertThat(encodePacket(new Packet(Packet.MESSAGE)).matches("[0-9]"), is(true)); @Override
public void call(String data) {
assertThat(data.matches("[0-9].*"), is(true));
}
});
encodePacket(new Packet<String>(Packet.MESSAGE), new EncodeCallback<String>() {
@Override
public void call(String data) {
assertThat(data.matches("[0-9]"), is(true));
}
});
} }
@Test @Test
public void decodeBadFormat() { public void decodeBadFormat() {
Packet p = decodePacket(":::"); Packet<String> p = decodePacket(":::");
assertThat(p.type, is(Packet.ERROR)); assertThat(p.type, is(Packet.ERROR));
assertThat(p.data, is(ERROR_DATA)); assertThat(p.data, is(ERROR_DATA));
} }
@Test @Test
public void decodeInexistentTypes() { public void decodeInexistentTypes() {
Packet p = decodePacket("94103"); Packet<String> p = decodePacket("94103");
assertThat(p.type, is(Packet.ERROR)); assertThat(p.type, is(Packet.ERROR));
assertThat(p.data, is(ERROR_DATA)); assertThat(p.data, is(ERROR_DATA));
} }
@Test @Test
public void encodePayloadsAsString() { public void encodePayloads() {
assertThat(encodePayload(new Packet[] { encodePayload(new Packet[]{new Packet(Packet.PING), new Packet(Packet.PONG)}, new EncodeCallback<byte[]>() {
new Packet(Packet.PING), new Packet(Packet.PONG)}), isA(String.class)); @Override
public void call(byte[] data) {
assertThat(data, isA(byte[].class));
}
});
} }
@Test @Test
public void encodeAndDecodePayloads() { public void encodeAndDecodePayloads() {
decodePayload(encodePayload(new Packet[] {new Packet(Packet.MESSAGE, "a")}), encodePayload(new Packet[] {new Packet<String>(Packet.MESSAGE, "a")}, new EncodeCallback<byte[]>() {
new DecodePayloadCallback() { @Override
public void call(byte[] data) {
decodePayload(data, new DecodePayloadCallback() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
@@ -107,9 +168,12 @@ public class ParserTest {
return true; return true;
} }
}); });
decodePayload(encodePayload(new Packet[] { }
new Packet(Packet.MESSAGE, "a"), new Packet(Packet.PING)}), });
new DecodePayloadCallback() { encodePayload(new Packet[]{new Packet<String>(Packet.MESSAGE, "a"), new Packet(Packet.PING)}, new EncodeCallback<byte[]>() {
@Override
public void call(byte[] data) {
decodePayload(data, new DecodePayloadCallback() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
@@ -121,26 +185,33 @@ public class ParserTest {
return true; return true;
} }
}); });
}
});
} }
@Test @Test
public void encodeAndDecodeEmptyPayloads() { public void encodeAndDecodeEmptyPayloads() {
decodePayload(encodePayload(new Packet[] {}), new DecodePayloadCallback() { encodePayload(new Packet[] {}, new EncodeCallback<byte[]>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public void call(byte[] data) {
assertThat(packet.type, is(Packet.OPEN)); decodePayload(data, new DecodePayloadCallback() {
boolean isLast = index + 1 == total; @Override
assertThat(isLast, is(true)); public boolean call(Packet packet, int index, int total) {
return true; assertThat(packet.type, is(Packet.OPEN));
boolean isLast = index + 1 == total;
assertThat(isLast, is(true));
return true;
}
});
} }
}); });
} }
@Test @Test
public void decodePayloadBadFormat() { public void decodePayloadBadFormat() {
decodePayload("1!", new DecodePayloadCallback() { decodePayload("1!", new DecodePayloadCallback<String>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet<String> packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
assertThat(packet.type, is(Packet.ERROR)); assertThat(packet.type, is(Packet.ERROR));
assertThat(packet.data, is(ERROR_DATA)); assertThat(packet.data, is(ERROR_DATA));
@@ -148,9 +219,9 @@ public class ParserTest {
return true; return true;
} }
}); });
decodePayload("", new DecodePayloadCallback() { decodePayload("", new DecodePayloadCallback<String>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet<String> packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
assertThat(packet.type, is(Packet.ERROR)); assertThat(packet.type, is(Packet.ERROR));
assertThat(packet.data, is(ERROR_DATA)); assertThat(packet.data, is(ERROR_DATA));
@@ -158,9 +229,9 @@ public class ParserTest {
return true; return true;
} }
}); });
decodePayload("))", new DecodePayloadCallback() { decodePayload("))", new DecodePayloadCallback<String>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet<String> packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
assertThat(packet.type, is(Packet.ERROR)); assertThat(packet.type, is(Packet.ERROR));
assertThat(packet.data, is(ERROR_DATA)); assertThat(packet.data, is(ERROR_DATA));
@@ -172,9 +243,9 @@ public class ParserTest {
@Test @Test
public void decodePayloadBadLength() { public void decodePayloadBadLength() {
decodePayload("1:", new DecodePayloadCallback() { decodePayload("1:", new DecodePayloadCallback<String>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet<String> packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
assertThat(packet.type, is(Packet.ERROR)); assertThat(packet.type, is(Packet.ERROR));
assertThat(packet.data, is(ERROR_DATA)); assertThat(packet.data, is(ERROR_DATA));
@@ -186,9 +257,9 @@ public class ParserTest {
@Test @Test
public void decodePayloadBadPacketFormat() { public void decodePayloadBadPacketFormat() {
decodePayload("3:99:", new DecodePayloadCallback() { decodePayload("3:99:", new DecodePayloadCallback<String>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet<String> packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
assertThat(packet.type, is(Packet.ERROR)); assertThat(packet.type, is(Packet.ERROR));
assertThat(packet.data, is(ERROR_DATA)); assertThat(packet.data, is(ERROR_DATA));
@@ -196,9 +267,9 @@ public class ParserTest {
return true; return true;
} }
}); });
decodePayload("1:aa", new DecodePayloadCallback() { decodePayload("1:aa", new DecodePayloadCallback<String>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet<String> packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
assertThat(packet.type, is(Packet.ERROR)); assertThat(packet.type, is(Packet.ERROR));
assertThat(packet.data, is(ERROR_DATA)); assertThat(packet.data, is(ERROR_DATA));
@@ -206,9 +277,9 @@ public class ParserTest {
return true; return true;
} }
}); });
decodePayload("1:a2:b", new DecodePayloadCallback() { decodePayload("1:a2:b", new DecodePayloadCallback<String>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet<String> packet, int index, int total) {
boolean isLast = index + 1 == total; boolean isLast = index + 1 == total;
assertThat(packet.type, is(Packet.ERROR)); assertThat(packet.type, is(Packet.ERROR));
assertThat(packet.data, is(ERROR_DATA)); assertThat(packet.data, is(ERROR_DATA));

View File

@@ -3,6 +3,6 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"engine.io": "0.8.2" "engine.io": "1.0.5"
} }
} }