diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..439755f9
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,34 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: '0 0 * * 0'
+
+jobs:
+ verify:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ java: [7, 8, 11]
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup java
+ uses: actions/setup-java@v1
+ with:
+ java-version: ${{ matrix.java }}
+ - name: Cache Maven packages
+ uses: actions/cache@v2
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+ restore-keys: ${{ runner.os }}-m2
+ - name: Setup Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 14.x
+ - name: Run the Maven verify phase
+ run: mvn verify -Dgpg.skip=true
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 84c2bc29..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-language: java
-sudo: false
-install: mvn install -DskipTests=true -Dgpg.skip=true
-jdk:
- - openjdk7
- - oraclejdk8
-dist: trusty
diff --git a/History.md b/History.md
index 1d187084..6adbf3cf 100644
--- a/History.md
+++ b/History.md
@@ -1,4 +1,24 @@
+2.0.0 / 2020-12-11
+==================
+
+### Features
+
+* add an extraHeaders option ([dfe65e3](https://github.com/socketio/engine.io-client-java/commit/dfe65e3b3b5eab4c3fddb9dfbf53d684fe461043))
+* add support for Engine.IO v4 ([41f89a3](https://github.com/socketio/engine.io-client-java/commit/41f89a38b7594f54ee9906bc91051874a60b690d))
+
+### Bug Fixes
+
+* check the type of the initial packet ([2b5dfb9](https://github.com/socketio/engine.io-client-java/commit/2b5dfb99f8f865362ddc0a17f52e8b70269d7572))
+
+
+1.0.1 / 2020-12-10
+==================
+
+### Bug Fixes
+
+* handle responses without content type ([#101](https://github.com/socketio/engine.io-client-java/issues/101)) ([6f065b7](https://github.com/socketio/engine.io-client-java/commit/6f065b7a62603730979d43cec71af0046ca4ab7c))
+
1.0.0 / 2017-07-14
==================
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..cc752f8b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+help: ## print this message
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
+
+build-site: ## build the site
+ mvn javadoc:javadoc site -DskipTests
+
+.PHONY: build-site
diff --git a/README.md b/README.md
index 8abe70ae..643fbdc4 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,31 @@
# Engine.IO-client Java
-[](https://travis-ci.org/socketio/engine.io-client-java)
+
+[](https://github.com/socketio/engine.io-client-java/actions)
This is the Engine.IO Client Library for Java, which is simply ported from the [JavaScript client](https://github.com/socketio/engine.io-client).
See also: [Socket.IO-client Java](https://github.com/socketio/socket.io-client-java)
+## Table of content
+
+- [Compatibility](#compatibility)
+- [Installation](#installation)
+ - [Maven](#maven)
+ - [Gradle](#gradle)
+- [Usage](#usage)
+- [Features](#features)
+- [License](#license)
+
+## Compatibility
+
+| Client version | Engine.IO server | Socket.IO server |
+| -------------- | ---------------- | ---------------- |
+| 0.9.x | 1.x | 1.x |
+| 1.x | 3.x | 2.x |
+| 2.x | 4.x | 3.x |
+
## Installation
-The latest artifact is available on Maven Central. To install manually, please refer [dependencies](https://socketio.github.io/engine.io-client-java/dependencies.html).
+The latest artifact is available on Maven Central.
### Maven
Add the following dependency to your `pom.xml`.
@@ -16,7 +35,7 @@ Add the following dependency to your `pom.xml`.
io.socketengine.io-client
- 1.0.0
+ 2.0.0
```
@@ -25,17 +44,12 @@ Add the following dependency to your `pom.xml`.
Add it as a gradle dependency for Android Studio, in `build.gradle`:
```groovy
-compile ('io.socket:engine.io-client:1.0.0') {
+compile ('io.socket:engine.io-client:2.0.0') {
// excluding org.json which is provided by Android
exclude group: 'org.json', module: 'json'
}
```
-#### Engine.IO Server 1.x suppport
-
-The current version of engine.io-client-java doesn't support engine.io server 1.x.
-Please use engine.io-client-java 0.9.x for that instead.
-
## Usage
Engine.IO-client Java has the similar api with the JS client. You can use `Socket` to connect:
@@ -118,43 +132,6 @@ socket = new Socket(opts);
## Features
This library supports all of the features the JS client does, including events, options and upgrading transport. Android is fully supported.
-### Extra features only for Java client
-
-#### Accessing HTTP Headers
-You can access HTTP headers like the following.
-
-```java
-socket.on(Socket.EVENT_TRANSPORT, new Emitter.listener() {
- @Override
- public void call(Object... args) {
- // Called on a new transport created.
- Transport transport = (Transport)args[0];
-
- transport.on(Transport.EVENT_REQUEST_HEADERS, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- @SuppressWarnings("unchecked")
- Map> headers = (Map>)args[0];
- // send cookie value to server.
- headers.put("Cookie", Arrays.asList("foo=1;"));
- }
- }).on(Transport.EVENT_RESPONSE_HEADERS, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- @SuppressWarnings("unchecked")
- Map> headers = (Map>)args[0];
- // receive cookie value from server.
- String cookie = headers.get("Set-Cookie").get(0);
- }
- });
- }
-});
-```
-
-See the Javadoc for more details.
-
-http://socketio.github.io/engine.io-client-java/apidocs/
-
## License
MIT
diff --git a/pom.xml b/pom.xml
index e722fc2f..3e9e3c97 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0io.socketengine.io-client
- 1.0.1-SNAPSHOT
+ 2.0.1-SNAPSHOTjarengine.io-clientEngine.IO Client Library for Java
@@ -49,7 +49,7 @@
com.squareup.okhttp3okhttp
- 3.8.1
+ 3.12.12org.json
@@ -200,20 +200,9 @@
2.3
- com.github.github
- site-maven-plugin
- 0.12
-
- Creating site for ${project.version}
-
-
-
-
- site
-
- site
-
-
+ org.apache.maven.plugins
+ maven-site-plugin
+ 3.9.1
diff --git a/src/main/java/io/socket/engineio/client/Socket.java b/src/main/java/io/socket/engineio/client/Socket.java
index 3f61f9ad..02de6bd5 100644
--- a/src/main/java/io/socket/engineio/client/Socket.java
+++ b/src/main/java/io/socket/engineio/client/Socket.java
@@ -4,16 +4,9 @@
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -37,6 +30,8 @@ public class Socket extends Emitter {
private static final Logger logger = Logger.getLogger(Socket.class.getName());
+ private static final AtomicInteger HEARTBEAT_THREAD_COUNTER = new AtomicInteger();
+
private static final String PROBE_ERROR = "probe error";
@@ -128,16 +123,16 @@ public String toString() {
/*package*/ LinkedList writeBuffer = new LinkedList();
/*package*/ Transport transport;
private Future pingTimeoutTimer;
- private Future pingIntervalTimer;
private okhttp3.WebSocket.Factory webSocketFactory;
private okhttp3.Call.Factory callFactory;
+ private final Map> extraHeaders;
private ReadyState readyState;
private ScheduledExecutorService heartbeatScheduler;
private final Listener onHeartbeatAsListener = new Listener() {
@Override
public void call(Object... args) {
- Socket.this.onHeartbeat(args.length > 0 ? (Long)args[0]: 0);
+ Socket.this.onHeartbeat();
}
};
@@ -222,6 +217,7 @@ public Socket(Options opts) {
}
webSocketFactory = defaultOkHttpClient;
}
+ this.extraHeaders = opts.extraHeaders;
}
public static void setDefaultOkHttpWebSocketFactory(okhttp3.WebSocket.Factory factory) {
@@ -294,6 +290,7 @@ private Transport createTransport(String name) {
opts.policyPort = options != null ? options.policyPort : this.policyPort;
opts.callFactory = options != null ? options.callFactory : this.callFactory;
opts.webSocketFactory = options != null ? options.webSocketFactory : this.webSocketFactory;
+ opts.extraHeaders = this.extraHeaders;
Transport transport;
if (WebSocket.NAME.equals(name)) {
@@ -540,9 +537,14 @@ private void onPacket(Packet packet) {
} catch (JSONException e) {
this.emit(EVENT_ERROR, new EngineIOException(e));
}
- } else if (Packet.PONG.equals(packet.type)) {
- this.setPing();
- this.emit(EVENT_PONG);
+ } else if (Packet.PING.equals(packet.type)) {
+ this.emit(EVENT_PING);
+ EventThread.exec(new Runnable() {
+ @Override
+ public void run() {
+ Socket.this.sendPacket(Packet.PONG, null);
+ }
+ });
} else if (Packet.ERROR.equals(packet.type)) {
EngineIOException err = new EngineIOException("server error");
err.code = packet.data;
@@ -568,20 +570,18 @@ private void onHandshake(HandshakeData data) {
this.onOpen();
// In case open handler closes socket
if (ReadyState.CLOSED == this.readyState) return;
- this.setPing();
+ this.onHeartbeat();
this.off(EVENT_HEARTBEAT, this.onHeartbeatAsListener);
this.on(EVENT_HEARTBEAT, this.onHeartbeatAsListener);
}
- private void onHeartbeat(long timeout) {
+ private void onHeartbeat() {
if (this.pingTimeoutTimer != null) {
pingTimeoutTimer.cancel(false);
}
- if (timeout <= 0) {
- timeout = this.pingInterval + this.pingTimeout;
- }
+ long timeout = this.pingInterval + this.pingTimeout;
final Socket self = this;
this.pingTimeoutTimer = this.getHeartbeatScheduler().schedule(new Runnable() {
@@ -598,46 +598,6 @@ public void run() {
}, timeout, TimeUnit.MILLISECONDS);
}
- private void setPing() {
- if (this.pingIntervalTimer != null) {
- pingIntervalTimer.cancel(false);
- }
-
- final Socket self = this;
- this.pingIntervalTimer = this.getHeartbeatScheduler().schedule(new Runnable() {
- @Override
- public void run() {
- EventThread.exec(new Runnable() {
- @Override
- public void run() {
- if (logger.isLoggable(Level.FINE)) {
- logger.fine(String.format("writing ping packet - expecting pong within %sms", self.pingTimeout));
- }
- self.ping();
- self.onHeartbeat(self.pingTimeout);
- }
- });
- }
- }, this.pingInterval, TimeUnit.MILLISECONDS);
- }
-
- /**
- * Sends a ping packet.
- */
- private void ping() {
- EventThread.exec(new Runnable() {
- @Override
- public void run() {
- Socket.this.sendPacket(Packet.PING, new Runnable() {
- @Override
- public void run() {
- Socket.this.emit(EVENT_PING);
- }
- });
- }
- });
- }
-
private void onDrain() {
for (int i = 0; i < this.prevBufferLen; i++) {
this.writeBuffer.poll();
@@ -833,9 +793,6 @@ private void onClose(String reason, Exception desc) {
final Socket self = this;
// clear timers
- if (this.pingIntervalTimer != null) {
- this.pingIntervalTimer.cancel(false);
- }
if (this.pingTimeoutTimer != null) {
this.pingTimeoutTimer.cancel(false);
}
@@ -886,11 +843,22 @@ public String id() {
private ScheduledExecutorService getHeartbeatScheduler() {
if (this.heartbeatScheduler == null || this.heartbeatScheduler.isShutdown()) {
- this.heartbeatScheduler = Executors.newSingleThreadScheduledExecutor();
+ this.heartbeatScheduler = createHeartbeatScheduler();
}
return this.heartbeatScheduler;
}
+ private ScheduledExecutorService createHeartbeatScheduler() {
+ return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = new Thread(r, "engine.io-client.heartbeat-" + HEARTBEAT_THREAD_COUNTER.getAndIncrement());
+ thread.setDaemon(true);
+ return thread;
+ }
+ });
+ }
+
public static class Options extends Transport.Options {
/**
diff --git a/src/main/java/io/socket/engineio/client/Transport.java b/src/main/java/io/socket/engineio/client/Transport.java
index 442c7762..7a8ce5cf 100644
--- a/src/main/java/io/socket/engineio/client/Transport.java
+++ b/src/main/java/io/socket/engineio/client/Transport.java
@@ -1,13 +1,13 @@
package io.socket.engineio.client;
+import java.util.List;
import java.util.Map;
import io.socket.emitter.Emitter;
import io.socket.engineio.parser.Packet;
import io.socket.engineio.parser.Parser;
import io.socket.thread.EventThread;
-import io.socket.utf8.UTF8Exception;
import okhttp3.Call;
import okhttp3.WebSocket;
@@ -44,6 +44,7 @@ public String toString() {
protected ReadyState readyState;
protected WebSocket.Factory webSocketFactory;
protected Call.Factory callFactory;
+ protected Map> extraHeaders;
public Transport(Options opts) {
this.path = opts.path;
@@ -56,6 +57,7 @@ public Transport(Options opts) {
this.socket = opts.socket;
this.webSocketFactory = opts.webSocketFactory;
this.callFactory = opts.callFactory;
+ this.extraHeaders = opts.extraHeaders;
}
protected Transport onError(String msg, Exception desc) {
@@ -96,11 +98,7 @@ public void send(final Packet[] packets) {
@Override
public void run() {
if (Transport.this.readyState == ReadyState.OPEN) {
- try {
- Transport.this.write(packets);
- } catch (UTF8Exception err) {
- throw new RuntimeException(err);
- }
+ Transport.this.write(packets);
} else {
throw new RuntimeException("Transport not open");
}
@@ -131,7 +129,7 @@ protected void onClose() {
this.emit(EVENT_CLOSE);
}
- abstract protected void write(Packet[] packets) throws UTF8Exception;
+ abstract protected void write(Packet[] packets);
abstract protected void doOpen();
@@ -151,5 +149,6 @@ public static class Options {
protected Socket socket;
public WebSocket.Factory webSocketFactory;
public Call.Factory callFactory;
+ public Map> extraHeaders;
}
}
diff --git a/src/main/java/io/socket/engineio/client/transports/Polling.java b/src/main/java/io/socket/engineio/client/transports/Polling.java
index 9341d9ac..3ac62f27 100644
--- a/src/main/java/io/socket/engineio/client/transports/Polling.java
+++ b/src/main/java/io/socket/engineio/client/transports/Polling.java
@@ -7,7 +7,6 @@
import io.socket.engineio.parser.Parser;
import io.socket.parseqs.ParseQS;
import io.socket.thread.EventThread;
-import io.socket.utf8.UTF8Exception;
import io.socket.yeast.Yeast;
import java.util.HashMap;
@@ -115,7 +114,7 @@ private void _onData(Object data) {
Parser.DecodePayloadCallback callback = new Parser.DecodePayloadCallback() {
@Override
public boolean call(Packet packet, int index, int total) {
- if (self.readyState == ReadyState.OPENING) {
+ if (self.readyState == ReadyState.OPENING && Packet.OPEN.equals(packet.type)) {
self.onOpen();
}
@@ -129,13 +128,7 @@ public boolean call(Packet packet, int index, int total) {
}
};
- if (data instanceof String) {
- @SuppressWarnings("unchecked")
- Parser.DecodePayloadCallback tempCallback = callback;
- Parser.decodePayload((String)data, tempCallback);
- } else if (data instanceof byte[]) {
- Parser.decodePayload((byte[])data, callback);
- }
+ Parser.decodePayload((String) data, callback);
if (this.readyState != ReadyState.CLOSED) {
this.polling = false;
@@ -158,11 +151,7 @@ protected void doClose() {
@Override
public void call(Object... args) {
logger.fine("writing close packet");
- try {
- self.write(new Packet[]{new Packet(Packet.CLOSE)});
- } catch (UTF8Exception err) {
- throw new RuntimeException(err);
- }
+ self.write(new Packet[]{new Packet(Packet.CLOSE)});
}
};
@@ -177,7 +166,7 @@ public void call(Object... args) {
}
}
- protected void write(Packet[] packets) throws UTF8Exception {
+ protected void write(Packet[] packets) {
final Polling self = this;
this.writable = false;
final Runnable callbackfn = new Runnable() {
@@ -188,16 +177,10 @@ public void run() {
}
};
- Parser.encodePayload(packets, new Parser.EncodeCallback() {
+ Parser.encodePayload(packets, new Parser.EncodeCallback() {
@Override
- public void call(Object data) {
- 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);
- }
+ public void call(String data) {
+ self.doWrite(data, callbackfn);
}
});
}
@@ -229,8 +212,6 @@ protected String uri() {
return schema + "://" + (ipv6 ? "[" + this.hostname + "]" : this.hostname) + port + this.path + derivedQuery;
}
- abstract protected void doWrite(byte[] data, Runnable fn);
-
abstract protected void doWrite(String data, Runnable fn);
abstract protected void doPoll();
diff --git a/src/main/java/io/socket/engineio/client/transports/PollingXHR.java b/src/main/java/io/socket/engineio/client/transports/PollingXHR.java
index ed118eb3..11ec6b38 100644
--- a/src/main/java/io/socket/engineio/client/transports/PollingXHR.java
+++ b/src/main/java/io/socket/engineio/client/transports/PollingXHR.java
@@ -43,6 +43,7 @@ protected Request request(Request.Options opts) {
}
opts.uri = this.uri();
opts.callFactory = this.callFactory;
+ opts.extraHeaders = this.extraHeaders;
Request req = new Request(opts);
@@ -67,20 +68,12 @@ public void run() {
return req;
}
- @Override
- 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();
opts.method = "POST";
opts.data = data;
+ opts.extraHeaders = this.extraHeaders;
Request req = this.request(opts);
final PollingXHR self = this;
req.on(Request.EVENT_SUCCESS, new Emitter.Listener() {
@@ -121,11 +114,7 @@ public void call(final Object... args) {
@Override
public void run() {
Object arg = args.length > 0 ? args[0] : null;
- if (arg instanceof String) {
- self.onData((String)arg);
- } else if (arg instanceof byte[]) {
- self.onData((byte[])arg);
- }
+ self.onData((String)arg);
}
});
}
@@ -153,18 +142,17 @@ public static class Request extends Emitter {
public static final String EVENT_REQUEST_HEADERS = "requestHeaders";
public static final String EVENT_RESPONSE_HEADERS = "responseHeaders";
- 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 uri;
- private Object data;
+ private String data;
private Call.Factory callFactory;
+ private Map> extraHeaders;
private Response response;
private Call requestCall;
@@ -173,19 +161,18 @@ public Request(Options opts) {
this.uri = opts.uri;
this.data = opts.data;
this.callFactory = opts.callFactory != null ? opts.callFactory : new OkHttpClient();
+ this.extraHeaders = opts.extraHeaders;
}
public void create() {
final Request self = this;
if (LOGGABLE_FINE) logger.fine(String.format("xhr open %s: %s", this.method, this.uri));
Map> headers = new TreeMap>(String.CASE_INSENSITIVE_ORDER);
-
+ if (this.extraHeaders != null) {
+ headers.putAll(this.extraHeaders);
+ }
if ("POST".equals(this.method)) {
- if (this.data instanceof byte[]) {
- headers.put("Content-type", new LinkedList(Collections.singletonList(BINARY_CONTENT_TYPE)));
- } else {
- headers.put("Content-type", new LinkedList(Collections.singletonList(TEXT_CONTENT_TYPE)));
- }
+ headers.put("Content-type", new LinkedList(Collections.singletonList(TEXT_CONTENT_TYPE)));
}
headers.put("Accept", new LinkedList(Collections.singletonList("*/*")));
@@ -193,8 +180,7 @@ public void create() {
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, this.data));
}
okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
@@ -204,10 +190,8 @@ public void create() {
}
}
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);
+ if (this.data != null) {
+ body = RequestBody.create(TEXT_MEDIA_TYPE, this.data);
}
okhttp3.Request request = requestBuilder
@@ -249,11 +233,6 @@ private void onData(String data) {
this.onSuccess();
}
- private void onData(byte[] data) {
- this.emit(EVENT_DATA, data);
- this.onSuccess();
- }
-
private void onError(Exception err) {
this.emit(EVENT_ERROR, err);
}
@@ -268,14 +247,9 @@ private void onResponseHeaders(Map> headers) {
private void onLoad() {
ResponseBody body = response.body();
- String contentType = body.contentType().toString();
try {
- if (BINARY_CONTENT_TYPE.equalsIgnoreCase(contentType)) {
- this.onData(body.bytes());
- } else {
- this.onData(body.string());
- }
+ this.onData(body.string());
} catch (IOException e) {
this.onError(e);
}
@@ -285,8 +259,9 @@ public static class Options {
public String uri;
public String method;
- public Object data;
+ public String data;
public Call.Factory callFactory;
+ public Map> extraHeaders;
}
}
}
diff --git a/src/main/java/io/socket/engineio/client/transports/WebSocket.java b/src/main/java/io/socket/engineio/client/transports/WebSocket.java
index d6e2f1a3..c2153c35 100644
--- a/src/main/java/io/socket/engineio/client/transports/WebSocket.java
+++ b/src/main/java/io/socket/engineio/client/transports/WebSocket.java
@@ -1,18 +1,11 @@
package io.socket.engineio.client.transports;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.logging.Logger;
-
import io.socket.engineio.client.Transport;
import io.socket.engineio.parser.Packet;
import io.socket.engineio.parser.Parser;
import io.socket.parseqs.ParseQS;
import io.socket.thread.EventThread;
-import io.socket.utf8.UTF8Exception;
import io.socket.yeast.Yeast;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -20,6 +13,12 @@
import okhttp3.WebSocketListener;
import okio.ByteString;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Logger;
+
public class WebSocket extends Transport {
@@ -36,6 +35,9 @@ public WebSocket(Options opts) {
protected void doOpen() {
Map> headers = new TreeMap>(String.CASE_INSENSITIVE_ORDER);
+ if (this.extraHeaders != null) {
+ headers.putAll(this.extraHeaders);
+ }
this.emit(EVENT_REQUEST_HEADERS, headers);
final WebSocket self = this;
@@ -111,7 +113,7 @@ public void run() {
});
}
- protected void write(Packet[] packets) throws UTF8Exception {
+ protected void write(Packet[] packets) {
final WebSocket self = this;
this.writable = false;
diff --git a/src/main/java/io/socket/engineio/parser/Base64.java b/src/main/java/io/socket/engineio/parser/Base64.java
new file mode 100644
index 00000000..d56a7cea
--- /dev/null
+++ b/src/main/java/io/socket/engineio/parser/Base64.java
@@ -0,0 +1,665 @@
+// from https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/Base64.java
+package io.socket.engineio.parser;
+
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Utilities for encoding and decoding the Base64 representation of
+ * binary data. See RFCs 2045 and 3548.
+ */
+public class Base64 {
+ /**
+ * Default values for encoder/decoder flags.
+ */
+ public static final int DEFAULT = 0;
+ /**
+ * Encoder flag bit to omit the padding '=' characters at the end
+ * of the output (if any).
+ */
+ public static final int NO_PADDING = 1;
+ /**
+ * Encoder flag bit to omit all line terminators (i.e., the output
+ * will be on one long line).
+ */
+ public static final int NO_WRAP = 2;
+ /**
+ * Encoder flag bit to indicate lines should be terminated with a
+ * CRLF pair instead of just an LF. Has no effect if {@code
+ * NO_WRAP} is specified as well.
+ */
+ public static final int CRLF = 4;
+ /**
+ * Encoder/decoder flag bit to indicate using the "URL and
+ * filename safe" variant of Base64 (see RFC 3548 section 4) where
+ * {@code -} and {@code _} are used in place of {@code +} and
+ * {@code /}.
+ */
+ public static final int URL_SAFE = 8;
+ /**
+ * Flag to pass to Base64OutputStream to indicate that it
+ * should not close the output stream it is wrapping when it
+ * itself is closed.
+ */
+ public static final int NO_CLOSE = 16;
+ // --------------------------------------------------------
+ // shared code
+ // --------------------------------------------------------
+ /* package */ static abstract class Coder {
+ public byte[] output;
+ public int op;
+ /**
+ * Encode/decode another block of input data. this.output is
+ * provided by the caller, and must be big enough to hold all
+ * the coded data. On exit, this.opwill be set to the length
+ * of the coded data.
+ *
+ * @param finish true if this is the final call to process for
+ * this object. Will finalize the coder state and
+ * include any final bytes in the output.
+ *
+ * @return true if the input so far is good; false if some
+ * error has been detected in the input stream..
+ */
+ public abstract boolean process(byte[] input, int offset, int len, boolean finish);
+ /**
+ * @return the maximum number of bytes a call to process()
+ * could produce for the given number of input bytes. This may
+ * be an overestimate.
+ */
+ public abstract int maxOutputSize(int len);
+ }
+ // --------------------------------------------------------
+ // decoding
+ // --------------------------------------------------------
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ *
The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param str the input String to decode, which is converted to
+ * bytes using the default charset
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(String str, int flags) {
+ return decode(str.getBytes(), flags);
+ }
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ *
The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the input array to decode
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int flags) {
+ return decode(input, 0, input.length, flags);
+ }
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ *
The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the data to decode
+ * @param offset the position within the input array at which to start
+ * @param len the number of bytes of input to decode
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int offset, int len, int flags) {
+ // Allocate space for the most data the input could represent.
+ // (It could contain less if it contains whitespace, etc.)
+ Decoder decoder = new Decoder(flags, new byte[len*3/4]);
+ if (!decoder.process(input, offset, len, true)) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ // Maybe we got lucky and allocated exactly enough output space.
+ if (decoder.op == decoder.output.length) {
+ return decoder.output;
+ }
+ // Need to shorten the array, so allocate a new one of the
+ // right size and copy.
+ byte[] temp = new byte[decoder.op];
+ System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
+ return temp;
+ }
+ /* package */ static class Decoder extends Coder {
+ /**
+ * Lookup table for turning bytes into their position in the
+ * Base64 alphabet.
+ */
+ private static final int DECODE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+ /**
+ * Decode lookup table for the "web safe" variant (RFC 3548
+ * sec. 4) where - and _ replace + and /.
+ */
+ private static final int DECODE_WEBSAFE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+ /** Non-data values in the DECODE arrays. */
+ private static final int SKIP = -1;
+ private static final int EQUALS = -2;
+ /**
+ * States 0-3 are reading through the next input tuple.
+ * State 4 is having read one '=' and expecting exactly
+ * one more.
+ * State 5 is expecting no more data or padding characters
+ * in the input.
+ * State 6 is the error state; an error has been detected
+ * in the input and no future input can "fix" it.
+ */
+ private int state; // state number (0 to 6)
+ private int value;
+ final private int[] alphabet;
+ public Decoder(int flags, byte[] output) {
+ this.output = output;
+ alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE;
+ state = 0;
+ value = 0;
+ }
+ /**
+ * @return an overestimate for the number of bytes {@code
+ * len} bytes could decode to.
+ */
+ public int maxOutputSize(int len) {
+ return len * 3/4 + 10;
+ }
+ /**
+ * Decode another block of input data.
+ *
+ * @return true if the state machine is still healthy. false if
+ * bad base-64 data has been detected in the input stream.
+ */
+ public boolean process(byte[] input, int offset, int len, boolean finish) {
+ if (this.state == 6) return false;
+ int p = offset;
+ len += offset;
+ // Using local variables makes the decoder about 12%
+ // faster than if we manipulate the member variables in
+ // the loop. (Even alphabet makes a measurable
+ // difference, which is somewhat surprising to me since
+ // the member variable is final.)
+ int state = this.state;
+ int value = this.value;
+ int op = 0;
+ final byte[] output = this.output;
+ final int[] alphabet = this.alphabet;
+ while (p < len) {
+ // Try the fast path: we're starting a new tuple and the
+ // next four bytes of the input stream are all data
+ // bytes. This corresponds to going through states
+ // 0-1-2-3-0. We expect to use this method for most of
+ // the data.
+ //
+ // If any of the next four bytes of input are non-data
+ // (whitespace, etc.), value will end up negative. (All
+ // the non-data values in decode are small negative
+ // numbers, so shifting any of them up and or'ing them
+ // together will result in a value with its top bit set.)
+ //
+ // You can remove this whole block and the output should
+ // be the same, just slower.
+ if (state == 0) {
+ while (p+4 <= len &&
+ (value = ((alphabet[input[p] & 0xff] << 18) |
+ (alphabet[input[p+1] & 0xff] << 12) |
+ (alphabet[input[p+2] & 0xff] << 6) |
+ (alphabet[input[p+3] & 0xff]))) >= 0) {
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ p += 4;
+ }
+ if (p >= len) break;
+ }
+ // The fast path isn't available -- either we've read a
+ // partial tuple, or the next four input bytes aren't all
+ // data, or whatever. Fall back to the slower state
+ // machine implementation.
+ int d = alphabet[input[p++] & 0xff];
+ switch (state) {
+ case 0:
+ if (d >= 0) {
+ value = d;
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+ case 1:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+ case 2:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect exactly one more padding character.
+ output[op++] = (byte) (value >> 4);
+ state = 4;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+ case 3:
+ if (d >= 0) {
+ // Emit the output triple and return to state 0.
+ value = (value << 6) | d;
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ state = 0;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect no further data or padding characters.
+ output[op+1] = (byte) (value >> 2);
+ output[op] = (byte) (value >> 10);
+ op += 2;
+ state = 5;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+ case 4:
+ if (d == EQUALS) {
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+ case 5:
+ if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+ }
+ }
+ if (!finish) {
+ // We're out of input, but a future call could provide
+ // more.
+ this.state = state;
+ this.value = value;
+ this.op = op;
+ return true;
+ }
+ // Done reading input. Now figure out where we are left in
+ // the state machine and finish up.
+ switch (state) {
+ case 0:
+ // Output length is a multiple of three. Fine.
+ break;
+ case 1:
+ // Read one extra input byte, which isn't enough to
+ // make another output byte. Illegal.
+ this.state = 6;
+ return false;
+ case 2:
+ // Read two extra input bytes, enough to emit 1 more
+ // output byte. Fine.
+ output[op++] = (byte) (value >> 4);
+ break;
+ case 3:
+ // Read three extra input bytes, enough to emit 2 more
+ // output bytes. Fine.
+ output[op++] = (byte) (value >> 10);
+ output[op++] = (byte) (value >> 2);
+ break;
+ case 4:
+ // Read one padding '=' when we expected 2. Illegal.
+ this.state = 6;
+ return false;
+ case 5:
+ // Read all the padding '='s we expected and no more.
+ // Fine.
+ break;
+ }
+ this.state = state;
+ this.op = op;
+ return true;
+ }
+ }
+ // --------------------------------------------------------
+ // encoding
+ // --------------------------------------------------------
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static String encodeToString(byte[] input, int flags) {
+ try {
+ return new String(encode(input, flags), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ // US-ASCII is guaranteed to be available.
+ throw new AssertionError(e);
+ }
+ }
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * start
+ * @param len the number of bytes of input to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static String encodeToString(byte[] input, int offset, int len, int flags) {
+ try {
+ return new String(encode(input, offset, len, flags), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ // US-ASCII is guaranteed to be available.
+ throw new AssertionError(e);
+ }
+ }
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static byte[] encode(byte[] input, int flags) {
+ return encode(input, 0, input.length, flags);
+ }
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * start
+ * @param len the number of bytes of input to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static byte[] encode(byte[] input, int offset, int len, int flags) {
+ Encoder encoder = new Encoder(flags, null);
+ // Compute the exact length of the array we will produce.
+ int output_len = len / 3 * 4;
+ // Account for the tail of the data and the padding bytes, if any.
+ if (encoder.do_padding) {
+ if (len % 3 > 0) {
+ output_len += 4;
+ }
+ } else {
+ switch (len % 3) {
+ case 0: break;
+ case 1: output_len += 2; break;
+ case 2: output_len += 3; break;
+ }
+ }
+ // Account for the newlines, if any.
+ if (encoder.do_newline && len > 0) {
+ output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) *
+ (encoder.do_cr ? 2 : 1);
+ }
+ encoder.output = new byte[output_len];
+ encoder.process(input, offset, len, true);
+ assert encoder.op == output_len;
+ return encoder.output;
+ }
+ /* package */ static class Encoder extends Coder {
+ /**
+ * Emit a new line every this many output tuples. Corresponds to
+ * a 76-character line length (the maximum allowable according to
+ * RFC 2045).
+ */
+ public static final int LINE_GROUPS = 19;
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+ };
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE_WEBSAFE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
+ };
+ final private byte[] tail;
+ /* package */ int tailLen;
+ private int count;
+ final public boolean do_padding;
+ final public boolean do_newline;
+ final public boolean do_cr;
+ final private byte[] alphabet;
+ public Encoder(int flags, byte[] output) {
+ this.output = output;
+ do_padding = (flags & NO_PADDING) == 0;
+ do_newline = (flags & NO_WRAP) == 0;
+ do_cr = (flags & CRLF) != 0;
+ alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
+ tail = new byte[2];
+ tailLen = 0;
+ count = do_newline ? LINE_GROUPS : -1;
+ }
+ /**
+ * @return an overestimate for the number of bytes {@code
+ * len} bytes could encode to.
+ */
+ public int maxOutputSize(int len) {
+ return len * 8/5 + 10;
+ }
+ public boolean process(byte[] input, int offset, int len, boolean finish) {
+ // Using local variables makes the encoder about 9% faster.
+ final byte[] alphabet = this.alphabet;
+ final byte[] output = this.output;
+ int op = 0;
+ int count = this.count;
+ int p = offset;
+ len += offset;
+ int v = -1;
+ // First we need to concatenate the tail of the previous call
+ // with any input bytes available now and see if we can empty
+ // the tail.
+ switch (tailLen) {
+ case 0:
+ // There was no tail.
+ break;
+ case 1:
+ if (p+2 <= len) {
+ // A 1-byte tail with at least 2 bytes of
+ // input available now.
+ v = ((tail[0] & 0xff) << 16) |
+ ((input[p++] & 0xff) << 8) |
+ (input[p++] & 0xff);
+ tailLen = 0;
+ };
+ break;
+ case 2:
+ if (p+1 <= len) {
+ // A 2-byte tail with at least 1 byte of input.
+ v = ((tail[0] & 0xff) << 16) |
+ ((tail[1] & 0xff) << 8) |
+ (input[p++] & 0xff);
+ tailLen = 0;
+ }
+ break;
+ }
+ if (v != -1) {
+ output[op++] = alphabet[(v >> 18) & 0x3f];
+ output[op++] = alphabet[(v >> 12) & 0x3f];
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (--count == 0) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ count = LINE_GROUPS;
+ }
+ }
+ // At this point either there is no tail, or there are fewer
+ // than 3 bytes of input available.
+ // The main loop, turning 3 input bytes into 4 output bytes on
+ // each iteration.
+ while (p+3 <= len) {
+ v = ((input[p] & 0xff) << 16) |
+ ((input[p+1] & 0xff) << 8) |
+ (input[p+2] & 0xff);
+ output[op] = alphabet[(v >> 18) & 0x3f];
+ output[op+1] = alphabet[(v >> 12) & 0x3f];
+ output[op+2] = alphabet[(v >> 6) & 0x3f];
+ output[op+3] = alphabet[v & 0x3f];
+ p += 3;
+ op += 4;
+ if (--count == 0) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ count = LINE_GROUPS;
+ }
+ }
+ if (finish) {
+ // Finish up the tail of the input. Note that we need to
+ // consume any bytes in tail before any bytes
+ // remaining in input; there should be at most two bytes
+ // total.
+ if (p-tailLen == len-1) {
+ int t = 0;
+ v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4;
+ tailLen -= t;
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (p-tailLen == len-2) {
+ int t = 0;
+ v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) |
+ (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2);
+ tailLen -= t;
+ output[op++] = alphabet[(v >> 12) & 0x3f];
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (do_newline && op > 0 && count != LINE_GROUPS) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ assert tailLen == 0;
+ assert p == len;
+ } else {
+ // Save the leftovers in tail to be consumed on the next
+ // call to encodeInternal.
+ if (p == len-1) {
+ tail[tailLen++] = input[p];
+ } else if (p == len-2) {
+ tail[tailLen++] = input[p];
+ tail[tailLen++] = input[p+1];
+ }
+ }
+ this.op = op;
+ this.count = count;
+ return true;
+ }
+ }
+
+ private Base64() { } // don't instantiate
+}
diff --git a/src/main/java/io/socket/engineio/parser/Parser.java b/src/main/java/io/socket/engineio/parser/Parser.java
index fe6c3622..f92e1e08 100644
--- a/src/main/java/io/socket/engineio/parser/Parser.java
+++ b/src/main/java/io/socket/engineio/parser/Parser.java
@@ -1,20 +1,13 @@
package io.socket.engineio.parser;
-
-import io.socket.utf8.UTF8;
-import io.socket.utf8.UTF8Exception;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
public class Parser {
- private static final int MAX_INT_CHAR_LENGTH = String.valueOf(Integer.MAX_VALUE).length();
+ public static final int PROTOCOL = 4;
- public static final int PROTOCOL = 3;
+ private static final char SEPARATOR = '\u001e';
private static final Map packets = new HashMap() {{
put(Packet.OPEN, 0);
@@ -26,61 +19,38 @@ public class Parser {
put(Packet.NOOP, 6);
}};
- private static final Map packetslist = new HashMap();
+ private static final Map packetslist = new HashMap<>();
static {
for (Map.Entry entry : packets.entrySet()) {
packetslist.put(entry.getValue(), entry.getKey());
}
}
- private static Packet err = new Packet(Packet.ERROR, "parser error");
-
- private static UTF8.Options utf8Options = new UTF8.Options();
- static {
- utf8Options.strict = false;
- }
-
+ private static final Packet err = new Packet(Packet.ERROR, "parser error");
private Parser() {}
- public static void encodePacket(Packet packet, EncodeCallback callback) throws UTF8Exception {
- encodePacket(packet, false, callback);
- }
-
- public static void encodePacket(Packet packet, boolean utf8encode, EncodeCallback callback) throws UTF8Exception {
+ public static void encodePacket(Packet packet, EncodeCallback callback) {
if (packet.data instanceof byte[]) {
- @SuppressWarnings("unchecked")
- Packet packetToEncode = packet;
- @SuppressWarnings("unchecked")
- EncodeCallback callbackToEncode = callback;
- encodeByteArray(packetToEncode, callbackToEncode);
- return;
- }
-
- String encoded = String.valueOf(packets.get(packet.type));
-
- if (null != packet.data) {
- encoded += utf8encode ? UTF8.encode(String.valueOf(packet.data), utf8Options) : String.valueOf(packet.data);
+ ((EncodeCallback) callback).call(((Packet) packet).data);
+ } else {
+ String type = String.valueOf(packets.get(packet.type));
+ String content = packet.data != null ? String.valueOf(packet.data) : "";
+ ((EncodeCallback) callback).call(type + content);
}
-
- @SuppressWarnings("unchecked")
- EncodeCallback tempCallback = callback;
- tempCallback.call(encoded);
}
- private static void encodeByteArray(Packet packet, EncodeCallback callback) {
- 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);
+ private static void encodePacketAsBase64(Packet packet, EncodeCallback callback) {
+ if (packet.data instanceof byte[]) {
+ byte[] data = ((Packet) packet).data;
+ String value = "b" + Base64.encodeToString(data, Base64.DEFAULT);
+ callback.call(value);
+ } else {
+ encodePacket(packet, callback);
+ }
}
public static Packet decodePacket(String data) {
- return decodePacket(data, false);
- }
-
- public static Packet decodePacket(String data, boolean utf8decode) {
if (data == null) {
return err;
}
@@ -92,14 +62,6 @@ public static Packet decodePacket(String data, boolean utf8decode) {
type = -1;
}
- if (utf8decode) {
- try {
- data = UTF8.decode(data, utf8Options);
- } catch (UTF8Exception e) {
- return err;
- }
- }
-
if (type < 0 || type >= packetslist.size()) {
return err;
}
@@ -111,93 +73,44 @@ public static Packet decodePacket(String data, boolean utf8decode) {
}
}
- public static Packet 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(packetslist.get(type), intArray);
- }
-
- public static void encodePayload(Packet[] packets, EncodeCallback callback) throws UTF8Exception {
- for (Packet packet : packets) {
- if (packet.data instanceof byte[]) {
- @SuppressWarnings("unchecked")
- EncodeCallback _callback = (EncodeCallback) callback;
- encodePayloadAsBinary(packets, _callback);
- return;
- }
- }
-
- if (packets.length == 0) {
- callback.call("0:");
- return;
+ public static Packet decodeBase64Packet(String data) {
+ if (data == null) {
+ return err;
}
- 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));
- }
- });
+ if (data.charAt(0) == 'b') {
+ return new Packet(Packet.MESSAGE, Base64.decode(data.substring(1), Base64.DEFAULT));
+ } else {
+ return decodePacket(data);
}
-
- callback.call(result.toString());
}
- private static String setLengthHeader(String message) {
- return message.length() + ":" + message;
+ public static Packet decodePacket(byte[] data) {
+ return new Packet<>(Packet.MESSAGE, data);
}
- private static void encodePayloadAsBinary(Packet[] packets, EncodeCallback callback) throws UTF8Exception {
+ public static void encodePayload(Packet[] packets, EncodeCallback callback) {
if (packets.length == 0) {
- callback.call(new byte[0]);
+ callback.call("0:");
return;
}
- final ArrayList results = new ArrayList(packets.length);
+ final StringBuilder result = new StringBuilder();
- for (Packet packet : packets) {
- encodeOneBinaryPacket(packet, new EncodeCallback() {
+ for (int i = 0, l = packets.length; i < l; i++) {
+ final boolean isLast = i == l - 1;
+ encodePacketAsBase64(packets[i], new EncodeCallback() {
@Override
- public void call(byte[] data) {
- results.add(data);
+ public void call(String message) {
+ result.append(message);
+ if (!isLast) {
+ result.append(SEPARATOR);
+ }
}
});
}
- callback.call(Buffer.concat(results.toArray(new byte[results.size()][])));
- }
-
- private static void encodeOneBinaryPacket(Packet p, final EncodeCallback 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}));
- }
- });
+ callback.call(result.toString());
}
public static void decodePayload(String data, DecodePayloadCallback callback) {
@@ -206,159 +119,30 @@ public static void decodePayload(String data, DecodePayloadCallback call
return;
}
- StringBuilder length = new StringBuilder();
- for (int i = 0, l = data.length(); i < l; i++) {
- char chr = data.charAt(i);
-
- if (':' != chr) {
- length.append(chr);
- continue;
- }
+ String[] messages = data.split(String.valueOf(SEPARATOR));
- int n;
- try {
- n = Integer.parseInt(length.toString());
- } catch (NumberFormatException e) {
+ for (int i = 0, l = messages.length; i < l; i++) {
+ Packet packet = decodeBase64Packet(messages[i]);
+ if (err.type.equals(packet.type) && err.data.equals(packet.data)) {
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);
+ boolean ret = callback.call(packet, i, l);
+ if (!ret) {
return;
}
-
- if (msg.length() != 0) {
- Packet 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) {
- callback.call(err, 0, 1);
}
}
- public static void decodePayload(byte[] data, DecodePayloadCallback callback) {
- ByteBuffer bufferTail = ByteBuffer.wrap(data);
- List