Merge pull request #93 from socketio/fix/compatible-v3

Update to make compatible with engine.io v3
This commit is contained in:
Naoyuki Kanezawa
2017-07-14 13:39:27 +09:00
committed by GitHub
10 changed files with 363 additions and 230 deletions

View File

@@ -1,5 +1,7 @@
language: java language: java
sudo: false
install: mvn install -DskipTests=true -Dgpg.skip=true install: mvn install -DskipTests=true -Dgpg.skip=true
jdk: jdk:
- openjdk7 - openjdk7
- oraclejdk8 - oraclejdk8
dist: trusty

View File

@@ -121,6 +121,7 @@ public class Socket extends Emitter {
private String path; private String path;
private String timestampParam; private String timestampParam;
private List<String> transports; private List<String> transports;
private Map<String, Transport.Options> transportOptions;
private List<String> upgrades; private List<String> upgrades;
private Map<String, String> query; private Map<String, String> query;
/*package*/ LinkedList<Packet> writeBuffer = new LinkedList<Packet>(); /*package*/ LinkedList<Packet> writeBuffer = new LinkedList<Packet>();
@@ -202,6 +203,8 @@ public class Socket extends Emitter {
this.timestampRequests = opts.timestampRequests; this.timestampRequests = opts.timestampRequests;
this.transports = new ArrayList<String>(Arrays.asList(opts.transports != null ? this.transports = new ArrayList<String>(Arrays.asList(opts.transports != null ?
opts.transports : new String[]{Polling.NAME, WebSocket.NAME})); opts.transports : new String[]{Polling.NAME, WebSocket.NAME}));
this.transportOptions = opts.transportOptions != null ?
opts.transportOptions : new HashMap<String, Transport.Options>();
this.policyPort = opts.policyPort != 0 ? opts.policyPort : 843; this.policyPort = opts.policyPort != 0 ? opts.policyPort : 843;
this.rememberUpgrade = opts.rememberUpgrade; this.rememberUpgrade = opts.rememberUpgrade;
this.callFactory = opts.callFactory != null ? opts.callFactory : defaultCallFactory; this.callFactory = opts.callFactory != null ? opts.callFactory : defaultCallFactory;
@@ -272,18 +275,22 @@ public class Socket extends Emitter {
query.put("sid", this.id); query.put("sid", this.id);
} }
// per-transport options
Transport.Options options = this.transportOptions.get(name);
Transport.Options opts = new Transport.Options(); Transport.Options opts = new Transport.Options();
opts.hostname = this.hostname;
opts.port = this.port;
opts.secure = this.secure;
opts.path = this.path;
opts.query = query; opts.query = query;
opts.timestampRequests = this.timestampRequests;
opts.timestampParam = this.timestampParam;
opts.policyPort = this.policyPort;
opts.socket = this; opts.socket = this;
opts.callFactory = this.callFactory;
opts.webSocketFactory = this.webSocketFactory; opts.hostname = options != null ? options.hostname : this.hostname;
opts.port = options != null ? options.port : this.port;
opts.secure = options != null ? options.secure : this.secure;
opts.path = options != null ? options.path : this.path;
opts.timestampRequests = options != null ? options.timestampRequests : this.timestampRequests;
opts.timestampParam = options != null ? options.timestampParam : this.timestampParam;
opts.policyPort = options != null ? options.policyPort : this.policyPort;
opts.callFactory = options != null ? options.callFactory : this.callFactory;
opts.webSocketFactory = options != null ? options.webSocketFactory : this.webSocketFactory;
Transport transport; Transport transport;
if (WebSocket.NAME.equals(name)) { if (WebSocket.NAME.equals(name)) {
@@ -866,7 +873,7 @@ public class Socket extends Emitter {
public boolean rememberUpgrade; public boolean rememberUpgrade;
public String host; public String host;
public String query; public String query;
public Map<String, Transport.Options> transportOptions;
private static Options fromURI(URI uri, Options opts) { private static Options fromURI(URI uri, Options opts) {
if (opts == null) { if (opts == null) {

View File

@@ -183,10 +183,16 @@ abstract public class Polling extends Transport {
} }
}; };
Parser.encodePayload(packets, new Parser.EncodeCallback<byte[]>() { Parser.encodePayload(packets, new Parser.EncodeCallback() {
@Override @Override
public void call(byte[] data) { public void call(Object data) {
self.doWrite(data, callbackfn); if (data instanceof byte[]) {
self.doWrite((byte[])data, callbackfn);
} else if (data instanceof String) {
self.doWrite((String)data, callbackfn);
} else {
logger.warning("Unexpected data: " + data);
}
} }
}); });
} }
@@ -220,5 +226,7 @@ abstract public class Polling extends Transport {
abstract protected void doWrite(byte[] data, Runnable fn); abstract protected void doWrite(byte[] data, Runnable fn);
abstract protected void doWrite(String data, Runnable fn);
abstract protected void doPoll(); abstract protected void doPoll();
} }

View File

@@ -8,6 +8,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import io.socket.emitter.Emitter; import io.socket.emitter.Emitter;
@@ -26,6 +27,8 @@ public class PollingXHR extends Polling {
private static final Logger logger = Logger.getLogger(PollingXHR.class.getName()); private static final Logger logger = Logger.getLogger(PollingXHR.class.getName());
private static boolean LOGGABLE_FINE = logger.isLoggable(Level.FINE);
public PollingXHR(Transport.Options opts) { public PollingXHR(Transport.Options opts) {
super(opts); super(opts);
} }
@@ -66,6 +69,15 @@ public class PollingXHR extends Polling {
@Override @Override
protected void doWrite(byte[] data, final Runnable fn) { protected void doWrite(byte[] data, final Runnable fn) {
this.doWrite((Object) data, fn);
}
@Override
protected void doWrite(String data, final Runnable fn) {
this.doWrite((Object) data, fn);
}
private void doWrite(Object 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;
@@ -140,13 +152,17 @@ public class PollingXHR extends Polling {
public static final String EVENT_ERROR = "error"; public static final String EVENT_ERROR = "error";
public static final String EVENT_REQUEST_HEADERS = "requestHeaders"; public static final String EVENT_REQUEST_HEADERS = "requestHeaders";
public static final String EVENT_RESPONSE_HEADERS = "responseHeaders"; public static final String EVENT_RESPONSE_HEADERS = "responseHeaders";
private static final String BINARY_CONTENT_TYPE = "application/octet-stream"; private static final String BINARY_CONTENT_TYPE = "application/octet-stream";
private static final String TEXT_CONTENT_TYPE = "text/plain;charset=UTF-8";
private static final MediaType BINARY_MEDIA_TYPE = MediaType.parse(BINARY_CONTENT_TYPE);
private static final MediaType TEXT_MEDIA_TYPE = MediaType.parse(TEXT_CONTENT_TYPE);
private String method; private String method;
private String uri; private String uri;
// data is always a binary private Object data;
private byte[] data;
private Call.Factory callFactory; private Call.Factory callFactory;
private Response response; private Response response;
@@ -161,28 +177,42 @@ public class PollingXHR extends Polling {
public void create() { public void create() {
final Request self = this; final Request self = this;
logger.fine(String.format("xhr open %s: %s", this.method, this.uri)); if (LOGGABLE_FINE) logger.fine(String.format("xhr open %s: %s", this.method, this.uri));
Map<String, List<String>> headers = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER); Map<String, List<String>> headers = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
if ("POST".equals(this.method)) { if ("POST".equals(this.method)) {
headers.put("Content-type", new LinkedList<String>(Collections.singletonList(BINARY_CONTENT_TYPE))); if (this.data instanceof byte[]) {
headers.put("Content-type", new LinkedList<String>(Collections.singletonList(BINARY_CONTENT_TYPE)));
} else {
headers.put("Content-type", new LinkedList<String>(Collections.singletonList(TEXT_CONTENT_TYPE)));
}
} }
headers.put("Accept", new LinkedList<String>(Collections.singletonList("*/*"))); headers.put("Accept", new LinkedList<String>(Collections.singletonList("*/*")));
self.onRequestHeaders(headers); this.onRequestHeaders(headers);
if (LOGGABLE_FINE) {
logger.fine(String.format("sending xhr with url %s | data %s", this.uri,
this.data instanceof byte[] ? Arrays.toString((byte[]) this.data) : this.data));
}
logger.fine(String.format("sending xhr with url %s | data %s", this.uri, Arrays.toString(this.data)));
okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder(); okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
for (Map.Entry<String, List<String>> header : headers.entrySet()) { for (Map.Entry<String, List<String>> header : headers.entrySet()) {
for (String v : header.getValue()){ for (String v : header.getValue()){
requestBuilder.addHeader(header.getKey(), v); requestBuilder.addHeader(header.getKey(), v);
} }
} }
RequestBody body = null;
if (this.data instanceof byte[]) {
body = RequestBody.create(BINARY_MEDIA_TYPE, (byte[])this.data);
} else if (this.data instanceof String) {
body = RequestBody.create(TEXT_MEDIA_TYPE, (String)this.data);
}
okhttp3.Request request = requestBuilder okhttp3.Request request = requestBuilder
.url(HttpUrl.parse(self.uri)) .url(HttpUrl.parse(self.uri))
.method(self.method, (self.data != null) ? .method(self.method, body)
RequestBody.create(MediaType.parse(BINARY_CONTENT_TYPE), self.data) : null)
.build(); .build();
requestCall = callFactory.newCall(request); requestCall = callFactory.newCall(request);
@@ -255,7 +285,7 @@ public class PollingXHR extends Polling {
public String uri; public String uri;
public String method; public String method;
public byte[] data; public Object data;
public Call.Factory callFactory; public Call.Factory callFactory;
} }
} }

View File

@@ -35,6 +35,11 @@ public class Parser {
private static Packet<String> err = new Packet<String>(Packet.ERROR, "parser error"); private static Packet<String> err = new Packet<String>(Packet.ERROR, "parser error");
private static UTF8.Options utf8Options = new UTF8.Options();
static {
utf8Options.strict = false;
}
private Parser() {} private Parser() {}
@@ -55,7 +60,7 @@ public class Parser {
String encoded = String.valueOf(packets.get(packet.type)); String encoded = String.valueOf(packets.get(packet.type));
if (null != packet.data) { if (null != packet.data) {
encoded += utf8encode ? UTF8.encode(String.valueOf(packet.data)) : String.valueOf(packet.data); encoded += utf8encode ? UTF8.encode(String.valueOf(packet.data), utf8Options) : String.valueOf(packet.data);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -89,7 +94,7 @@ public class Parser {
if (utf8decode) { if (utf8decode) {
try { try {
data = UTF8.decode(data); data = UTF8.decode(data, utf8Options);
} catch (UTF8Exception e) { } catch (UTF8Exception e) {
return err; return err;
} }
@@ -113,7 +118,40 @@ public class Parser {
return new Packet<byte[]>(packetslist.get(type), intArray); return new Packet<byte[]>(packetslist.get(type), intArray);
} }
public static void encodePayload(Packet[] packets, EncodeCallback<byte[]> callback) throws UTF8Exception { public static void encodePayload(Packet[] packets, EncodeCallback callback) throws UTF8Exception {
for (Packet packet : packets) {
if (packet.data instanceof byte[]) {
@SuppressWarnings("unchecked")
EncodeCallback<byte[]> _callback = (EncodeCallback<byte[]>) callback;
encodePayloadAsBinary(packets, _callback);
return;
}
}
if (packets.length == 0) {
callback.call("0:");
return;
}
final StringBuilder result = new StringBuilder();
for (Packet packet : packets) {
encodePacket(packet, false, new EncodeCallback() {
@Override
public void call(Object message) {
result.append(setLengthHeader((String)message));
}
});
}
callback.call(result.toString());
}
private static String setLengthHeader(String message) {
return message.length() + ":" + message;
}
private static void encodePayloadAsBinary(Packet[] packets, EncodeCallback<byte[]> callback) throws UTF8Exception {
if (packets.length == 0) { if (packets.length == 0) {
callback.call(new byte[0]); callback.call(new byte[0]);
return; return;
@@ -122,30 +160,10 @@ public class Parser {
final ArrayList<byte[]> results = new ArrayList<byte[]>(packets.length); final ArrayList<byte[]> results = new ArrayList<byte[]>(packets.length);
for (Packet packet : packets) { for (Packet packet : packets) {
encodePacket(packet, true, new EncodeCallback() { encodeOneBinaryPacket(packet, new EncodeCallback<byte[]>() {
@Override @Override
public void call(Object packet) { public void call(byte[] data) {
if (packet instanceof String) { results.add(data);
String encodingLength = String.valueOf(((String) packet).length());
byte[] sizeBuffer = new byte[encodingLength.length() + 2];
sizeBuffer[0] = (byte)0; // is a string
for (int i = 0; i < encodingLength.length(); i ++) {
sizeBuffer[i + 1] = (byte)Character.getNumericValue(encodingLength.charAt(i));
}
sizeBuffer[sizeBuffer.length - 1] = (byte)255;
results.add(Buffer.concat(new byte[][] {sizeBuffer, stringToByteArray((String)packet)}));
return;
}
String encodingLength = String.valueOf(((byte[])packet).length);
byte[] sizeBuffer = new byte[encodingLength.length() + 2];
sizeBuffer[0] = (byte)1; // is binary
for (int i = 0; i < encodingLength.length(); i ++) {
sizeBuffer[i + 1] = (byte)Character.getNumericValue(encodingLength.charAt(i));
}
sizeBuffer[sizeBuffer.length - 1] = (byte)255;
results.add(Buffer.concat(new byte[][] {sizeBuffer, (byte[])packet}));
} }
}); });
} }
@@ -153,6 +171,35 @@ public class Parser {
callback.call(Buffer.concat(results.toArray(new byte[results.size()][]))); callback.call(Buffer.concat(results.toArray(new byte[results.size()][])));
} }
private static void encodeOneBinaryPacket(Packet p, final EncodeCallback<byte[]> doneCallback) throws UTF8Exception {
encodePacket(p, true, new EncodeCallback() {
@Override
public void call(Object packet) {
if (packet instanceof String) {
String encodingLength = String.valueOf(((String) packet).length());
byte[] sizeBuffer = new byte[encodingLength.length() + 2];
sizeBuffer[0] = (byte)0; // is a string
for (int i = 0; i < encodingLength.length(); i ++) {
sizeBuffer[i + 1] = (byte)Character.getNumericValue(encodingLength.charAt(i));
}
sizeBuffer[sizeBuffer.length - 1] = (byte)255;
doneCallback.call(Buffer.concat(new byte[][] {sizeBuffer, stringToByteArray((String)packet)}));
return;
}
String encodingLength = String.valueOf(((byte[])packet).length);
byte[] sizeBuffer = new byte[encodingLength.length() + 2];
sizeBuffer[0] = (byte)1; // is binary
for (int i = 0; i < encodingLength.length(); i ++) {
sizeBuffer[i + 1] = (byte)Character.getNumericValue(encodingLength.charAt(i));
}
sizeBuffer[sizeBuffer.length - 1] = (byte)255;
doneCallback.call(Buffer.concat(new byte[][] {sizeBuffer, (byte[])packet}));
}
});
}
public static void decodePayload(String data, DecodePayloadCallback<String> callback) { public static void decodePayload(String data, DecodePayloadCallback<String> callback) {
if (data == null || data.length() == 0) { if (data == null || data.length() == 0) {
callback.call(err, 0, 1); callback.call(err, 0, 1);
@@ -165,37 +212,40 @@ public class Parser {
if (':' != chr) { if (':' != chr) {
length.append(chr); length.append(chr);
} else { continue;
int n;
try {
n = Integer.parseInt(length.toString());
} catch (NumberFormatException e) {
callback.call(err, 0, 1);
return;
}
String msg;
try {
msg = data.substring(i + 1, i + 1 + n);
} catch (IndexOutOfBoundsException e) {
callback.call(err, 0, 1);
return;
}
if (msg.length() != 0) {
Packet<String> packet = decodePacket(msg, true);
if (err.type.equals(packet.type) && err.data.equals(packet.data)) {
callback.call(err, 0, 1);
return;
}
boolean ret = callback.call(packet, i + n, l);
if (!ret) return;
}
i += n;
length = new StringBuilder();
} }
int n;
try {
n = Integer.parseInt(length.toString());
} catch (NumberFormatException e) {
callback.call(err, 0, 1);
return;
}
String msg;
try {
msg = data.substring(i + 1, i + 1 + n);
} catch (IndexOutOfBoundsException e) {
callback.call(err, 0, 1);
return;
}
if (msg.length() != 0) {
Packet<String> packet = decodePacket(msg, false);
if (err.type.equals(packet.type) && err.data.equals(packet.data)) {
callback.call(err, 0, 1);
return;
}
boolean ret = callback.call(packet, i + n, l);
if (!ret) {
return;
}
}
i += n;
length = new StringBuilder();
} }
if (length.length() > 0) { if (length.length() > 0) {
@@ -210,23 +260,17 @@ public class Parser {
while (bufferTail.capacity() > 0) { while (bufferTail.capacity() > 0) {
StringBuilder strLen = new StringBuilder(); StringBuilder strLen = new StringBuilder();
boolean isString = (bufferTail.get(0) & 0xFF) == 0; boolean isString = (bufferTail.get(0) & 0xFF) == 0;
boolean numberTooLong = false;
for (int i = 1; ; i++) { for (int i = 1; ; i++) {
int b = bufferTail.get(i) & 0xFF; int b = bufferTail.get(i) & 0xFF;
if (b == 255) break; if (b == 255) break;
// supports only integer // supports only integer
if (strLen.length() > MAX_INT_CHAR_LENGTH) { if (strLen.length() > MAX_INT_CHAR_LENGTH) {
numberTooLong = true; callback.call(err, 0, 1);
break; return;
} }
strLen.append(b); strLen.append(b);
} }
if (numberTooLong) {
@SuppressWarnings("unchecked")
DecodePayloadCallback<String> tempCallback = callback;
tempCallback.call(err, 0, 1);
return;
}
bufferTail.position(strLen.length() + 1); bufferTail.position(strLen.length() + 1);
bufferTail = bufferTail.slice(); bufferTail = bufferTail.slice();

View File

@@ -18,6 +18,12 @@ public final class UTF8 {
private UTF8 () {} private UTF8 () {}
public static String encode(String string) throws UTF8Exception { public static String encode(String string) throws UTF8Exception {
return encode(string, new Options());
}
public static String encode(String string, Options opts) throws UTF8Exception {
boolean strict = opts.strict;
int[] codePoints = ucs2decode(string); int[] codePoints = ucs2decode(string);
int length = codePoints.length; int length = codePoints.length;
int index = -1; int index = -1;
@@ -25,18 +31,24 @@ public final class UTF8 {
StringBuilder byteString = new StringBuilder(); StringBuilder byteString = new StringBuilder();
while (++index < length) { while (++index < length) {
codePoint = codePoints[index]; codePoint = codePoints[index];
byteString.append(encodeCodePoint(codePoint)); byteString.append(encodeCodePoint(codePoint, strict));
} }
return byteString.toString(); return byteString.toString();
} }
public static String decode(String byteString) throws UTF8Exception { public static String decode(String byteString) throws UTF8Exception {
return decode(byteString, new Options());
}
public static String decode(String byteString, Options opts) throws UTF8Exception {
boolean strict = opts.strict;
byteArray = ucs2decode(byteString); byteArray = ucs2decode(byteString);
byteCount = byteArray.length; byteCount = byteArray.length;
byteIndex = 0; byteIndex = 0;
List<Integer> codePoints = new ArrayList<Integer>(); List<Integer> codePoints = new ArrayList<Integer>();
int tmp; int tmp;
while ((tmp = decodeSymbol()) != -1) { while ((tmp = decodeSymbol(strict)) != -1) {
codePoints.add(tmp); codePoints.add(tmp);
} }
return ucs2encode(listToArray(codePoints)); return ucs2encode(listToArray(codePoints));
@@ -54,7 +66,7 @@ public final class UTF8 {
return output; return output;
} }
private static String encodeCodePoint(int codePoint) throws UTF8Exception { private static String encodeCodePoint(int codePoint, boolean strict) throws UTF8Exception {
StringBuilder symbol = new StringBuilder(); StringBuilder symbol = new StringBuilder();
if ((codePoint & 0xFFFFFF80) == 0) { if ((codePoint & 0xFFFFFF80) == 0) {
return symbol.append(Character.toChars(codePoint)).toString(); return symbol.append(Character.toChars(codePoint)).toString();
@@ -62,7 +74,9 @@ public final class UTF8 {
if ((codePoint & 0xFFFFF800) == 0) { if ((codePoint & 0xFFFFF800) == 0) {
symbol.append(Character.toChars(((codePoint >> 6) & 0x1F) | 0xC0)); symbol.append(Character.toChars(((codePoint >> 6) & 0x1F) | 0xC0));
} else if ((codePoint & 0xFFFF0000) == 0) { } else if ((codePoint & 0xFFFF0000) == 0) {
checkScalarValue(codePoint); if (!checkScalarValue(codePoint, strict)) {
codePoint = 0xFFFD;
}
symbol.append(Character.toChars(((codePoint >> 12) & 0x0F) | 0xE0)); symbol.append(Character.toChars(((codePoint >> 12) & 0x0F) | 0xE0));
symbol.append(createByte(codePoint, 6)); symbol.append(createByte(codePoint, 6));
} else if ((codePoint & 0xFFE00000) == 0) { } else if ((codePoint & 0xFFE00000) == 0) {
@@ -78,7 +92,7 @@ public final class UTF8 {
return Character.toChars(((codePoint >> shift) & 0x3F) | 0x80); return Character.toChars(((codePoint >> shift) & 0x3F) | 0x80);
} }
private static int decodeSymbol() throws UTF8Exception { private static int decodeSymbol(boolean strict) throws UTF8Exception {
int byte1; int byte1;
int byte2; int byte2;
int byte3; int byte3;
@@ -115,8 +129,7 @@ public final class UTF8 {
byte3 = readContinuationByte(); byte3 = readContinuationByte();
codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
if (codePoint >= 0x0800) { if (codePoint >= 0x0800) {
checkScalarValue(codePoint); return checkScalarValue(codePoint, strict) ? codePoint : 0xFFFD;
return codePoint;
} else { } else {
throw new UTF8Exception(INVALID_CONTINUATION_BYTE); throw new UTF8Exception(INVALID_CONTINUATION_BYTE);
} }
@@ -158,13 +171,17 @@ public final class UTF8 {
return output.toString(); return output.toString();
} }
private static void checkScalarValue(int codePoint) throws UTF8Exception { private static boolean checkScalarValue(int codePoint, boolean strict) throws UTF8Exception {
if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
throw new UTF8Exception( if (strict) {
"Lone surrogate U+" + Integer.toHexString(codePoint).toUpperCase() + throw new UTF8Exception(
" is not a scalar value" "Lone surrogate U+" + Integer.toHexString(codePoint).toUpperCase() +
); " is not a scalar value"
);
}
return false;
} }
return true;
} }
private static int[] listToArray(List<Integer> list) { private static int[] listToArray(List<Integer> list) {
@@ -175,4 +192,8 @@ public final class UTF8 {
} }
return array; return array;
} }
public static class Options {
public boolean strict = true;
}
} }

View File

@@ -156,6 +156,19 @@ public class ParserTest {
}); });
} }
@Test
public void encodingStringMessageWithLoneSurrogatesReplacedByUFFFD() throws UTF8Exception {
String data = "\uDC00\uD834\uDF06\uDC00 \uD800\uD835\uDF07\uD800";
encodePacket(new Packet<String>(Packet.MESSAGE, data), true, new EncodeCallback<String>() {
@Override
public void call(String encoded) {
Packet<String> p = decodePacket(encoded, true);
assertThat(p.type, is(Packet.MESSAGE));
assertThat(p.data, is("\uFFFD\uD834\uDF06\uFFFD \uFFFD\uD835\uDF07\uFFFD"));
}
});
}
@Test @Test
public void decodeEmptyPayload() { public void decodeEmptyPayload() {
Packet<String> p = decodePacket((String)null); Packet<String> p = decodePacket((String)null);
@@ -186,20 +199,20 @@ public class ParserTest {
@Test @Test
public void encodePayloads() throws UTF8Exception { public void encodePayloads() throws UTF8Exception {
encodePayload(new Packet[]{new Packet(Packet.PING), new Packet(Packet.PONG)}, new EncodeCallback<byte[]>() { encodePayload(new Packet[]{new Packet(Packet.PING), new Packet(Packet.PONG)}, new EncodeCallback<String>() {
@Override @Override
public void call(byte[] data) { public void call(String data) {
assertThat(data, isA(byte[].class)); assertThat(data, isA(String.class));
} }
}); });
} }
@Test @Test
public void encodeAndDecodePayloads() throws UTF8Exception { public void encodeAndDecodePayloads() throws UTF8Exception {
encodePayload(new Packet[] {new Packet<String>(Packet.MESSAGE, "a")}, new EncodeCallback<byte[]>() { encodePayload(new Packet[] {new Packet<String>(Packet.MESSAGE, "a")}, new EncodeCallback<String>() {
@Override @Override
public void call(byte[] data) { public void call(String data) {
decodePayload(data, new DecodePayloadCallback() { decodePayload(data, new DecodePayloadCallback<String>() {
@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;
@@ -209,10 +222,10 @@ public class ParserTest {
}); });
} }
}); });
encodePayload(new Packet[]{new Packet<String>(Packet.MESSAGE, "a"), new Packet(Packet.PING)}, new EncodeCallback<byte[]>() { encodePayload(new Packet[]{new Packet<String>(Packet.MESSAGE, "a"), new Packet(Packet.PING)}, new EncodeCallback<String>() {
@Override @Override
public void call(byte[] data) { public void call(String data) {
decodePayload(data, new DecodePayloadCallback() { decodePayload(data, new DecodePayloadCallback<String>() {
@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;
@@ -230,10 +243,10 @@ public class ParserTest {
@Test @Test
public void encodeAndDecodeEmptyPayloads() throws UTF8Exception { public void encodeAndDecodeEmptyPayloads() throws UTF8Exception {
encodePayload(new Packet[] {}, new EncodeCallback<byte[]>() { encodePayload(new Packet[] {}, new EncodeCallback<String>() {
@Override @Override
public void call(byte[] data) { public void call(String data) {
decodePayload(data, new DecodePayloadCallback() { decodePayload(data, new DecodePayloadCallback<String>() {
@Override @Override
public boolean call(Packet packet, int index, int total) { public boolean call(Packet packet, int index, int total) {
assertThat(packet.type, is(Packet.OPEN)); assertThat(packet.type, is(Packet.OPEN));
@@ -246,6 +259,19 @@ public class ParserTest {
}); });
} }
@Test
public void notUTF8EncodeWhenDealingWithStringsOnly() throws UTF8Exception {
encodePayload(new Packet[] {
new Packet(Packet.MESSAGE, "€€€"),
new Packet(Packet.MESSAGE, "α")
}, new EncodeCallback<String>() {
@Override
public void call(String data) {
assertThat(data, is("4:4€€€2:4α"));
}
});
}
@Test @Test
public void decodePayloadBadFormat() { public void decodePayloadBadFormat() {
decodePayload("1!", new DecodePayloadCallback<String>() { decodePayload("1!", new DecodePayloadCallback<String>() {
@@ -328,20 +354,6 @@ public class ParserTest {
}); });
} }
@Test
public void decodePayloadInvalidUTF8() {
decodePayload("2:4\uffff", new DecodePayloadCallback<String>() {
@Override
public boolean call(Packet<String> packet, int index, int total) {
boolean isLast = index + 1 == total;
assertThat(packet.type, is(Packet.ERROR));
assertThat(packet.data, is(ERROR_DATA));
assertThat(isLast, is(true));
return true;
}
});
}
@Test @Test
public void encodeBinaryMessage() throws UTF8Exception { public void encodeBinaryMessage() throws UTF8Exception {
final byte[] data = new byte[5]; final byte[] data = new byte[5];

View File

@@ -2,10 +2,34 @@
"requires": true, "requires": true,
"lockfileVersion": 1, "lockfileVersion": 1,
"dependencies": { "dependencies": {
"accepts": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
"integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
"requires": {
"mime-types": "2.1.15",
"negotiator": "0.6.1"
}
},
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
},
"arraybuffer.slice": { "arraybuffer.slice": {
"version": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", "version": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz",
"integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=" "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco="
}, },
"base64-arraybuffer": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
},
"base64id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
"integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY="
},
"blob": { "blob": {
"version": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", "version": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
"integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE="
@@ -15,122 +39,104 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
}, },
"debug": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
"integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
"requires": {
"ms": "2.0.0"
}
},
"engine.io": { "engine.io": {
"version": "1.8.4", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.4.tgz", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.0.tgz",
"integrity": "sha1-d7zhK4Dl1gQpM3/sOw2vaR68kAM=", "integrity": "sha1-XKQ4486f28kVxKIcjdnhJmcG5X4=",
"requires": { "requires": {
"accepts": "1.3.3", "accepts": "1.3.3",
"base64id": "1.0.0", "base64id": "1.0.0",
"cookie": "0.3.1", "cookie": "0.3.1",
"debug": "2.3.3", "debug": "2.6.8",
"engine.io-parser": "1.3.2", "engine.io-parser": "2.1.1",
"ws": "1.1.4" "uws": "0.14.5",
"ws": "2.3.1"
}
},
"engine.io-parser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.1.tgz",
"integrity": "sha1-4Ps/DgRi9/WLt3waUun1p+JuRmg=",
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz",
"base64-arraybuffer": "0.1.5",
"blob": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
"has-binary2": "1.0.2"
}
},
"has-binary2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz",
"integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=",
"requires": {
"isarray": "2.0.1"
}, },
"dependencies": { "dependencies": {
"accepts": { "isarray": {
"version": "1.3.3", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
"requires": {
"mime-types": "2.1.15",
"negotiator": "0.6.1"
}
},
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
},
"base64-arraybuffer": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
},
"base64id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
"integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY="
},
"debug": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
"integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
"requires": {
"ms": "0.7.2"
}
},
"engine.io-parser": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz",
"integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=",
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz",
"base64-arraybuffer": "0.1.5",
"blob": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
"has-binary": "0.1.7",
"wtf-8": "1.0.0"
}
},
"has-binary": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz",
"integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=",
"requires": {
"isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
}
},
"mime-db": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
"integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE="
},
"mime-types": {
"version": "2.1.15",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
"integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
"requires": {
"mime-db": "1.27.0"
}
},
"ms": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
"integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U="
},
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"ws": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-1.1.4.tgz",
"integrity": "sha1-V/QNA2gy5fUFVmKjl8Tedu1mv2E=",
"requires": {
"options": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
"ultron": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz"
}
} }
} }
}, },
"isarray": { "mime-db": {
"version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "version": "1.27.0",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
"integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE="
}, },
"options": { "mime-types": {
"version": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", "version": "2.1.15",
"integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
"integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
"requires": {
"mime-db": "1.27.0"
}
}, },
"ultron": { "ms": {
"version": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", "version": "2.0.0",
"integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}, },
"wtf-8": { "negotiator": {
"version": "1.0.0", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=" "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"safe-buffer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
"integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
},
"uws": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/uws/-/uws-0.14.5.tgz",
"integrity": "sha1-Z6rzPEaypYel9mZtAPdpEyjxSdw=",
"optional": true
},
"ws": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz",
"integrity": "sha1-a5Sz5EfLajY/eF6vlK9jWejoHIA=",
"requires": {
"safe-buffer": "5.0.1",
"ultron": "1.1.0"
},
"dependencies": {
"ultron": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz",
"integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ="
}
}
} }
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"dependencies": { "dependencies": {
"engine.io": "1.8.4" "engine.io": "3.1.0"
} }
} }

View File

@@ -11,7 +11,10 @@ if (process.env.SSL) {
http = require('http').createServer(); http = require('http').createServer();
} }
var server = engine.attach(http, {pingInterval: 500}); var server = engine.attach(http, {
pingInterval: 500,
wsEngine: 'ws'
});
var port = process.env.PORT || 3000 var port = process.env.PORT || 3000
http.listen(port, function() { http.listen(port, function() {