Initial commit
This commit is contained in:
19
src/main/java/com/github/nkzawa/socketio/client/Engine.java
Normal file
19
src/main/java/com/github/nkzawa/socketio/client/Engine.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.github.nkzawa.socketio.client;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
class Engine extends com.github.nkzawa.engineio.client.Socket {
|
||||
|
||||
Engine(URI uri, Options opts) {
|
||||
super(uri, opts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onopen() {}
|
||||
|
||||
@Override
|
||||
public void onmessage(String s) {}
|
||||
|
||||
@Override
|
||||
public void onclose() {}
|
||||
}
|
||||
73
src/main/java/com/github/nkzawa/socketio/client/IO.java
Normal file
73
src/main/java/com/github/nkzawa/socketio/client/IO.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package com.github.nkzawa.socketio.client;
|
||||
|
||||
|
||||
import com.github.nkzawa.socketio.parser.Parser;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class IO {
|
||||
|
||||
private static final Map<String, Manager> managers = new HashMap<String, Manager>();
|
||||
|
||||
public static int protocol = Parser.protocol;
|
||||
|
||||
private IO() {}
|
||||
|
||||
public static Socket socket(String uri) throws URISyntaxException {
|
||||
return socket(uri, null);
|
||||
}
|
||||
|
||||
public static Socket socket(String uri, Options opts) throws URISyntaxException {
|
||||
return socket(new URI(uri), opts);
|
||||
}
|
||||
|
||||
public static Socket socket(URI uri) throws URISyntaxException {
|
||||
return socket(uri, null);
|
||||
}
|
||||
|
||||
public static Socket socket(URI uri, Options opts) throws URISyntaxException {
|
||||
if (opts == null) {
|
||||
opts = new Options();
|
||||
}
|
||||
|
||||
URL parsed;
|
||||
try {
|
||||
parsed = Url.parse(uri);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new URISyntaxException(uri.toString(), e.getMessage());
|
||||
}
|
||||
URI href = parsed.toURI();
|
||||
Manager io;
|
||||
|
||||
if (opts.forceNew || !opts.multiplex) {
|
||||
io = new Manager(href, opts);
|
||||
} else {
|
||||
String id = Url.extractId(parsed);
|
||||
if (!managers.containsKey(id)) {
|
||||
managers.put(id, new Manager(href, opts));
|
||||
}
|
||||
io = managers.get(id);
|
||||
}
|
||||
|
||||
String path = uri.getPath();
|
||||
return io.socket(path != null && !path.isEmpty() ? path : "/");
|
||||
}
|
||||
|
||||
|
||||
public static class Options extends com.github.nkzawa.engineio.client.Socket.Options {
|
||||
|
||||
public boolean forceNew;
|
||||
public boolean multiplex = true;
|
||||
public boolean reconnection;
|
||||
public int reconnectionAttempts;
|
||||
public long reconnectionDelay;
|
||||
public long reconnectionDelayMax;
|
||||
public long timeout = -1;
|
||||
|
||||
}
|
||||
}
|
||||
323
src/main/java/com/github/nkzawa/socketio/client/Manager.java
Normal file
323
src/main/java/com/github/nkzawa/socketio/client/Manager.java
Normal file
@@ -0,0 +1,323 @@
|
||||
package com.github.nkzawa.socketio.client;
|
||||
|
||||
import com.github.nkzawa.emitter.Emitter;
|
||||
import com.github.nkzawa.socketio.parser.Packet;
|
||||
import com.github.nkzawa.socketio.parser.Parser;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Manager extends Emitter {
|
||||
|
||||
private static final Logger logger = Logger.getLogger("socket.io-client:manager");
|
||||
|
||||
/*package*/ static final int CLOSED = 0;
|
||||
/*package*/ static final int OPENING = 1;
|
||||
/*package*/ static final int OPEN = 2;
|
||||
|
||||
public static final String EVENT_OPEN = "open";
|
||||
public static final String EVENT_CLOSE = "close";
|
||||
public static final String EVENT_PACKET = "packet";
|
||||
public static final String EVENT_ERROR = "error";
|
||||
public static final String EVENT_CONNECT_ERROR = "connect_error";
|
||||
public static final String EVENT_CONNECT_TIMEOUT = "connect_timeout";
|
||||
public static final String EVENT_RECONNECT = "reconnect";
|
||||
public static final String EVENT_RECONNECT_FAILED = "reconnect_failed";
|
||||
public static final String EVENT_RECONNECT_ERROR = "reconnect_error";
|
||||
|
||||
/*package*/ int readyState = CLOSED;
|
||||
|
||||
private boolean _reconnection;
|
||||
private boolean skipReconnect;
|
||||
private boolean reconnecting;
|
||||
private int _reconnectionAttempts;
|
||||
private long _reconnectionDelay;
|
||||
private long _reconnectionDelayMax;
|
||||
private long _timeout;
|
||||
private AtomicInteger connected = new AtomicInteger();
|
||||
private AtomicInteger attempts = new AtomicInteger();
|
||||
private Map<String, Socket> nsps = new ConcurrentHashMap<String, Socket>();
|
||||
private Queue<On.Handle> subs = new ConcurrentLinkedQueue<On.Handle>();
|
||||
private com.github.nkzawa.engineio.client.Socket engine;
|
||||
|
||||
private ScheduledExecutorService timeoutScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public Manager(URI uri, IO.Options opts) {
|
||||
opts = initOptions(opts);
|
||||
this.engine = new Engine(uri, opts);
|
||||
}
|
||||
|
||||
public Manager(com.github.nkzawa.engineio.client.Socket socket, IO.Options opts) {
|
||||
opts = initOptions(opts);
|
||||
this.engine = socket;
|
||||
}
|
||||
|
||||
private IO.Options initOptions(IO.Options opts) {
|
||||
if (opts == null) {
|
||||
opts = new IO.Options();
|
||||
}
|
||||
if (opts.path == null) {
|
||||
opts.path = "/socket.io";
|
||||
}
|
||||
this.reconnection(opts.reconnection);
|
||||
this.reconnectionAttempts(opts.reconnectionAttempts != 0 ? opts.reconnectionAttempts : Integer.MAX_VALUE);
|
||||
this.reconnectionDelay(opts.reconnectionDelay != 0 ? opts.reconnectionDelay : 1000);
|
||||
this.reconnectionDelayMax(opts.reconnectionDelayMax != 0 ? opts.reconnectionDelayMax : 5000);
|
||||
this.timeout(opts.timeout < 0 ? 10000 : opts.timeout);
|
||||
return opts;
|
||||
}
|
||||
|
||||
public boolean reconnection() {
|
||||
return this._reconnection;
|
||||
}
|
||||
|
||||
public Manager reconnection(boolean v) {
|
||||
this._reconnection = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int reconnectionAttempts() {
|
||||
return this._reconnectionAttempts;
|
||||
}
|
||||
|
||||
public Manager reconnectionAttempts(int v) {
|
||||
this._reconnectionAttempts = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long reconnectionDelay() {
|
||||
return this._reconnectionDelay;
|
||||
}
|
||||
|
||||
public Manager reconnectionDelay(long v) {
|
||||
this._reconnectionDelay = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long reconnectionDelayMax() {
|
||||
return this._reconnectionDelayMax;
|
||||
}
|
||||
|
||||
public Manager reconnectionDelayMax(long v) {
|
||||
this._reconnectionDelayMax = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long timeout() {
|
||||
return this._timeout;
|
||||
}
|
||||
|
||||
public Manager timeout(long v) {
|
||||
this._timeout = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Manager open(){
|
||||
return open(null);
|
||||
}
|
||||
|
||||
public Manager open(final OpenCallback fn) {
|
||||
if (this.readyState == OPEN && !this.reconnecting) return this;
|
||||
|
||||
final com.github.nkzawa.engineio.client.Socket socket = this.engine;
|
||||
final Manager self = this;
|
||||
|
||||
this.readyState = OPENING;
|
||||
|
||||
final On.Handle openSub = On.on(socket, Engine.EVENT_OPEN, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
self.onopen();
|
||||
if (fn != null) fn.call(null);
|
||||
}
|
||||
});
|
||||
|
||||
On.Handle errorSub = On.on(socket, Engine.EVENT_ERROR, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
Object data = objects.length > 0 ? objects[0] : null;
|
||||
self.cleanup();
|
||||
self.emit(EVENT_CONNECT_ERROR, data);
|
||||
if (fn != null) {
|
||||
Exception err = new SocketIOException("Connection error",
|
||||
data instanceof Exception ? (Exception)data : null);
|
||||
fn.call(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this._timeout >= 0) {
|
||||
final long timeout = this._timeout;
|
||||
logger.info(String.format("connection attempt will timeout after %d", timeout));
|
||||
|
||||
final Future timer = timeoutScheduler.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
logger.info(String.format("connect attempt timed out after %d", timeout));
|
||||
openSub.destroy();
|
||||
socket.close();
|
||||
socket.emit(Engine.EVENT_ERROR, new SocketIOException("timeout"));
|
||||
self.emit(EVENT_CONNECT_TIMEOUT, timeout);
|
||||
}
|
||||
}, timeout, TimeUnit.MILLISECONDS);
|
||||
|
||||
On.Handle timeSub = new On.Handle() {
|
||||
@Override
|
||||
public void destroy() {
|
||||
timer.cancel(false);
|
||||
}
|
||||
};
|
||||
|
||||
this.subs.add(timeSub);
|
||||
}
|
||||
|
||||
this.subs.add(openSub);
|
||||
this.subs.add(errorSub);
|
||||
|
||||
this.engine.open();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void onopen() {
|
||||
this.cleanup();
|
||||
|
||||
this.readyState = OPEN;
|
||||
this.emit(EVENT_OPEN);
|
||||
|
||||
final com.github.nkzawa.engineio.client.Socket socket = this.engine;
|
||||
this.subs.add(On.on(socket, Engine.EVENT_DATA, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
Manager.this.ondata((String)objects[0]);
|
||||
}
|
||||
}));
|
||||
this.subs.add(On.on(socket, Engine.EVENT_ERROR, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
Manager.this.onerror((Exception)objects[0]);
|
||||
}
|
||||
}));
|
||||
this.subs.add(On.on(socket, Engine.EVENT_CLOSE, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
Manager.this.onclose();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void ondata(String data) {
|
||||
this.emit(EVENT_PACKET, Parser.decode(data));
|
||||
}
|
||||
|
||||
private void onerror(Exception err) {
|
||||
this.emit(EVENT_ERROR, err);
|
||||
}
|
||||
|
||||
public Socket socket(String nsp) {
|
||||
Socket socket = this.nsps.get(nsp);
|
||||
if (socket == null) {
|
||||
socket = new Socket(this, nsp);
|
||||
this.nsps.put(nsp, socket);
|
||||
final Manager self = this;
|
||||
socket.on(Socket.EVENT_CONNECT, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
self.connected.incrementAndGet();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
/*package*/ void destroy(Socket socket) {
|
||||
int connected = this.connected.decrementAndGet();
|
||||
if (connected == 0) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void packet(Packet packet) {
|
||||
logger.info(String.format("writing packet %s", packet));
|
||||
this.engine.write(Parser.encode(packet));
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
On.Handle sub;
|
||||
while ((sub = this.subs.poll()) != null) sub.destroy();
|
||||
}
|
||||
|
||||
private void close() {
|
||||
this.skipReconnect = true;
|
||||
this.cleanup();
|
||||
this.engine.close();
|
||||
}
|
||||
|
||||
private void onclose() {
|
||||
this.cleanup();
|
||||
if (!this.skipReconnect) {
|
||||
this.reconnect();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void reconnect() {
|
||||
final Manager self = this;
|
||||
int attempts = this.attempts.incrementAndGet();
|
||||
|
||||
if (attempts > this._reconnectionAttempts) {
|
||||
this.emit(EVENT_RECONNECT_FAILED);
|
||||
this.reconnecting = false;
|
||||
} else {
|
||||
long delay = this.attempts.get() * this.reconnectionDelay();
|
||||
delay = Math.min(delay, this.reconnectionDelayMax());
|
||||
logger.info(String.format("will wait %dms before reconnect attempt", delay));
|
||||
|
||||
this.reconnecting = true;
|
||||
final Future timer = this.reconnectScheduler.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
logger.info("attempting reconnect");
|
||||
self.open(new OpenCallback() {
|
||||
@Override
|
||||
public void call(Exception err) {
|
||||
if (err != null) {
|
||||
logger.info("reconnect attempt error");
|
||||
self.reconnect();
|
||||
self.emit(EVENT_RECONNECT_ERROR, err);
|
||||
} else {
|
||||
logger.info("reconnect success");
|
||||
self.onreconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
|
||||
this.subs.add(new On.Handle() {
|
||||
@Override
|
||||
public void destroy() {
|
||||
timer.cancel(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void onreconnect() {
|
||||
int attempts = this.attempts.get();
|
||||
this.attempts.set(0);
|
||||
this.reconnecting = false;
|
||||
this.emit(EVENT_RECONNECT, attempts);
|
||||
}
|
||||
|
||||
public static interface OpenCallback {
|
||||
|
||||
public void call(Exception err);
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/github/nkzawa/socketio/client/On.java
Normal file
23
src/main/java/com/github/nkzawa/socketio/client/On.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.github.nkzawa.socketio.client;
|
||||
|
||||
import com.github.nkzawa.emitter.Emitter;
|
||||
|
||||
public class On extends Emitter {
|
||||
|
||||
private On() {}
|
||||
|
||||
public static Handle on(final Emitter obj, final String ev, final Listener fn) {
|
||||
obj.on(ev, fn);
|
||||
return new Handle() {
|
||||
@Override
|
||||
public void destroy() {
|
||||
obj.off(ev, fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static interface Handle {
|
||||
|
||||
public void destroy();
|
||||
}
|
||||
}
|
||||
282
src/main/java/com/github/nkzawa/socketio/client/Socket.java
Normal file
282
src/main/java/com/github/nkzawa/socketio/client/Socket.java
Normal file
@@ -0,0 +1,282 @@
|
||||
package com.github.nkzawa.socketio.client;
|
||||
|
||||
import com.github.nkzawa.emitter.Emitter;
|
||||
import com.github.nkzawa.socketio.parser.Packet;
|
||||
import com.github.nkzawa.socketio.parser.Parser;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Socket extends Emitter {
|
||||
|
||||
private static final Logger logger = Logger.getLogger("socket.io-client:socket");
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
public static final String EVENT_CONNECT = "connect";
|
||||
public static final String EVENT_DISCONNECT = "disconnect";
|
||||
public static final String EVENT_MESSAGE = "message";
|
||||
public static final String EVENT_ERROR = "error";
|
||||
|
||||
private static List<String> events = new ArrayList<String>() {{
|
||||
add(EVENT_CONNECT);
|
||||
add(EVENT_DISCONNECT);
|
||||
add(EVENT_ERROR);
|
||||
}};
|
||||
|
||||
private boolean connected;
|
||||
private boolean disconnected = true;
|
||||
private AtomicInteger ids = new AtomicInteger();
|
||||
private String nsp;
|
||||
private Manager io;
|
||||
private Map<Integer, Ack> acks = new ConcurrentHashMap<Integer, Ack>();
|
||||
private Queue<On.Handle> subs;
|
||||
private final Queue<LinkedList<Object>> buffer = new ConcurrentLinkedQueue<LinkedList<Object>>();
|
||||
|
||||
public Socket(Manager io, String nsp) {
|
||||
this.io = io;
|
||||
this.nsp = nsp;
|
||||
}
|
||||
|
||||
public void open() {
|
||||
final Manager io = this.io;
|
||||
this.subs = new ConcurrentLinkedQueue<On.Handle>() {{
|
||||
add(On.on(io, Manager.EVENT_OPEN, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
Socket.this.onopen();
|
||||
}
|
||||
}));
|
||||
add(On.on(io, Manager.EVENT_ERROR, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
Socket.this.onerror(objects.length > 0 ? (Exception) objects[0] : null);
|
||||
}
|
||||
}));
|
||||
}};
|
||||
if (this.io.readyState == Manager.OPEN) this.onopen();
|
||||
io.open();
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
this.open();
|
||||
}
|
||||
|
||||
public Socket send(Object... args) {
|
||||
this.emit(EVENT_MESSAGE, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Emitter emit(String event, Object... args) {
|
||||
if (events.contains(event)) {
|
||||
super.emit(event, args);
|
||||
} else {
|
||||
LinkedList<Object> _args = new LinkedList<Object>(Arrays.asList(args));
|
||||
if (_args.peekLast() instanceof Ack) {
|
||||
Ack ack = (Ack)_args.pollLast();
|
||||
return this.emit(event, ack, _args.toArray());
|
||||
}
|
||||
|
||||
_args.offerFirst(event);
|
||||
Packet packet = new Packet(Parser.EVENT, gson.toJsonTree(_args.toArray()));
|
||||
this.packet(packet);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias method for `emit` with `ack`
|
||||
*
|
||||
* @param event
|
||||
* @param ack
|
||||
* @param args
|
||||
* @return
|
||||
*/
|
||||
public Emitter emit(final String event, Ack ack, final Object... args) {
|
||||
List<Object> _args = new ArrayList<Object>() {{
|
||||
add(event);
|
||||
addAll(Arrays.asList(args));
|
||||
}};
|
||||
Packet packet = new Packet(Parser.EVENT, gson.toJsonTree(_args.toArray()));
|
||||
|
||||
int ids = this.ids.getAndIncrement();
|
||||
logger.info(String.format("emitting packet with ack id %d", ids));
|
||||
this.acks.put(ids, ack);
|
||||
packet.id = ids;
|
||||
|
||||
this.packet(packet);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void packet(Packet packet) {
|
||||
packet.nsp = this.nsp;
|
||||
this.io.packet(packet);
|
||||
}
|
||||
|
||||
private void onerror(Exception err) {
|
||||
this.emit(EVENT_ERROR, err);
|
||||
}
|
||||
|
||||
private void onopen() {
|
||||
logger.info("transport is open - connecting");
|
||||
|
||||
if (!"/".equals(this.nsp)) {
|
||||
this.packet(new Packet(Parser.CONNECT));
|
||||
}
|
||||
|
||||
Manager io = this.io;
|
||||
this.subs.add(On.on(io, Manager.EVENT_PACKET, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
Packet packet = objects.length > 0 ? (Packet)objects[0] : null;
|
||||
Socket.this.onpacket(packet);
|
||||
}
|
||||
}));
|
||||
this.subs.add(On.on(io, Manager.EVENT_CLOSE, new Listener() {
|
||||
@Override
|
||||
public void call(Object... objects) {
|
||||
String reason = objects.length > 0 ? (String)objects[0] : null;
|
||||
Socket.this.onclose(reason);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void onclose(String reason) {
|
||||
logger.info(String.format("close (%s)", reason));
|
||||
this.connected = false;
|
||||
this.disconnected = true;
|
||||
this.emit(EVENT_DISCONNECT, reason);
|
||||
}
|
||||
|
||||
private void onpacket(Packet packet) {
|
||||
if (!this.nsp.equals(packet.nsp)) return;
|
||||
|
||||
switch (packet.type) {
|
||||
case Parser.CONNECT:
|
||||
this.onconnect();
|
||||
break;
|
||||
|
||||
case Parser.EVENT:
|
||||
this.onevent(packet);
|
||||
break;
|
||||
|
||||
case Parser.ACK:
|
||||
this.onack(packet);
|
||||
break;
|
||||
|
||||
case Parser.DISCONNECT:
|
||||
this.ondisconnect();
|
||||
break;
|
||||
|
||||
case Parser.ERROR:
|
||||
this.emit(EVENT_ERROR, packet.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onevent(Packet packet) {
|
||||
Type type = new TypeToken<LinkedList<Object>>(){}.getType();
|
||||
LinkedList<Object> args = gson.fromJson(packet.data != null ? packet.data : new JsonArray(), type);
|
||||
logger.info(String.format("emitting event %s", args));
|
||||
|
||||
if (packet.id >= 0) {
|
||||
logger.info("attaching ack callback to event");
|
||||
args.offerLast(this.ack(packet.id));
|
||||
}
|
||||
|
||||
if (this.connected) {
|
||||
String event = (String)args.pollFirst();
|
||||
super.emit(event, args.toArray());
|
||||
} else {
|
||||
this.buffer.add(args);
|
||||
}
|
||||
}
|
||||
|
||||
private Ack ack(final int id) {
|
||||
final Socket self = this;
|
||||
final boolean[] sent = new boolean[] {false};
|
||||
return new Ack() {
|
||||
@Override
|
||||
public synchronized void call(Object... args) {
|
||||
if (sent[0]) return;
|
||||
sent[0] = true;
|
||||
logger.info(String.format("sending ack %s", args));
|
||||
Packet packet = new Packet(Parser.ACK, gson.toJsonTree(args));
|
||||
packet.id = id;
|
||||
self.packet(packet);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void onack(Packet packet) {
|
||||
logger.info(String.format("calling ack %s with %s", packet.id, packet.data));
|
||||
Ack fn = this.acks.remove(packet.id);
|
||||
fn.call(gson.fromJson(packet.data, Object[].class));
|
||||
}
|
||||
|
||||
private void onconnect() {
|
||||
this.connected = true;
|
||||
this.disconnected = false;
|
||||
this.emit(EVENT_CONNECT);
|
||||
this.emitBuffered();
|
||||
}
|
||||
|
||||
private void emitBuffered() {
|
||||
synchronized (this.buffer) {
|
||||
LinkedList<Object> data;
|
||||
while ((data = this.buffer.poll()) != null) {
|
||||
String event = (String)data.pollFirst();
|
||||
super.emit(event, data.toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ondisconnect() {
|
||||
logger.info(String.format("server disconnect (%s)", this.nsp));
|
||||
this.destroy();
|
||||
this.onclose("io server disconnect");
|
||||
}
|
||||
|
||||
private void destroy() {
|
||||
logger.info(String.format("destroying socket (%s)", this.nsp));
|
||||
|
||||
for (On.Handle sub : this.subs) {
|
||||
sub.destroy();
|
||||
}
|
||||
|
||||
this.io.destroy(this);
|
||||
}
|
||||
|
||||
public Socket close() {
|
||||
if (!this.connected) return this;
|
||||
|
||||
logger.info(String.format("performing disconnect (%s)", this.nsp));
|
||||
this.packet(new Packet(Parser.DISCONNECT));
|
||||
|
||||
this.destroy();
|
||||
|
||||
this.onclose("io client disconnect");
|
||||
return this;
|
||||
}
|
||||
|
||||
public Socket disconnect() {
|
||||
return this.close();
|
||||
}
|
||||
|
||||
|
||||
public static interface Ack {
|
||||
|
||||
public void call(Object... args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.nkzawa.socketio.client;
|
||||
|
||||
public class SocketIOException extends Exception {
|
||||
|
||||
public SocketIOException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SocketIOException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SocketIOException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public SocketIOException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
34
src/main/java/com/github/nkzawa/socketio/client/Url.java
Normal file
34
src/main/java/com/github/nkzawa/socketio/client/Url.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.github.nkzawa.socketio.client;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
public class Url {
|
||||
|
||||
private Url() {
|
||||
}
|
||||
|
||||
public static URL parse(URI uri) throws MalformedURLException {
|
||||
String protocol = uri.getScheme();
|
||||
if (protocol == null || !protocol.matches("^https?|wss?$")) {
|
||||
uri = uri.resolve("https://" + uri.getAuthority());
|
||||
}
|
||||
String path = uri.getPath();
|
||||
if (path == null || path.isEmpty()) {
|
||||
uri = uri.resolve("/");
|
||||
}
|
||||
return uri.toURL();
|
||||
}
|
||||
|
||||
public static String extractId(URL url) {
|
||||
String protocol = url.getProtocol();
|
||||
int port = url.getPort();
|
||||
if ((protocol.matches("^http|ws$") && port == 80) ||
|
||||
(protocol.matches("^(http|ws)s$") && port == 443)) {
|
||||
port = -1;
|
||||
}
|
||||
return protocol + "://" + url.getHost() + (port != -1 ? ":" + port : "");
|
||||
}
|
||||
|
||||
}
|
||||
22
src/main/java/com/github/nkzawa/socketio/parser/Packet.java
Normal file
22
src/main/java/com/github/nkzawa/socketio/parser/Packet.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.github.nkzawa.socketio.parser;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
public class Packet {
|
||||
|
||||
public int type = -1;
|
||||
public int id = -1;
|
||||
public String nsp;
|
||||
public JsonElement data;
|
||||
|
||||
public Packet() {}
|
||||
|
||||
public Packet(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Packet(int type, JsonElement data) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
131
src/main/java/com/github/nkzawa/socketio/parser/Parser.java
Normal file
131
src/main/java/com/github/nkzawa/socketio/parser/Parser.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package com.github.nkzawa.socketio.parser;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Parser {
|
||||
|
||||
private static final Logger logger = Logger.getLogger("socket.io-parser");
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
private static final JsonParser parser = new JsonParser();
|
||||
|
||||
public static final int CONNECT = 0;
|
||||
public static final int DISCONNECT = 1;
|
||||
public static final int EVENT = 2;
|
||||
public static final int ACK = 3;
|
||||
public static final int ERROR = 4;
|
||||
|
||||
public static int protocol = 1;
|
||||
public static List<String > types = new ArrayList<String>() {{
|
||||
add("CONNECT");
|
||||
add("DISCONNECT");
|
||||
add("EVENT");
|
||||
add("ACK");
|
||||
add("ERROR");
|
||||
}};
|
||||
|
||||
private Parser() {}
|
||||
|
||||
public static String encode(Packet obj) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
boolean nsp = false;
|
||||
str.append(obj.type);
|
||||
|
||||
if (obj.nsp != null && !obj.nsp.isEmpty() && !"/".equals(obj.nsp)) {
|
||||
nsp = true;
|
||||
str.append(obj.nsp);
|
||||
}
|
||||
|
||||
if (obj.id >= 0) {
|
||||
if (nsp) {
|
||||
str.append(",");
|
||||
nsp = false;
|
||||
}
|
||||
str.append(obj.id);
|
||||
}
|
||||
|
||||
if (obj.data != null) {
|
||||
if (nsp) str.append(",");
|
||||
str.append(gson.toJson(obj.data));
|
||||
}
|
||||
|
||||
logger.info(String.format("encoded %s as %s", obj, str));
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static Packet decode(String str) {
|
||||
Packet p = new Packet();
|
||||
int i = 0;
|
||||
|
||||
try {
|
||||
p.type = Character.getNumericValue(str.charAt(0));
|
||||
types.get(p.type);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
return error();
|
||||
}
|
||||
|
||||
char next = Character.UNASSIGNED;
|
||||
try {
|
||||
next = str.charAt(i + 1);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// do nothing
|
||||
}
|
||||
if (next == '/') {
|
||||
StringBuilder nsp = new StringBuilder();
|
||||
while (true) {
|
||||
++i;
|
||||
char c = str.charAt(i);
|
||||
if (c == ',') break;
|
||||
nsp.append(c);
|
||||
if (i + 1 == str.length()) break;
|
||||
}
|
||||
p.nsp = nsp.toString();
|
||||
} else {
|
||||
p.nsp = "/";
|
||||
}
|
||||
|
||||
next = Character.UNASSIGNED;
|
||||
try {
|
||||
next = str.charAt(i + 1);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// do nothing
|
||||
}
|
||||
if (Character.getNumericValue(next) != -1) {
|
||||
StringBuilder id = new StringBuilder();
|
||||
while (true) {
|
||||
++i;
|
||||
Character c = str.charAt(i);
|
||||
if (c == null || Character.getNumericValue(c) == -1) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
id.append(c);
|
||||
if (i + 1 == str.length()) break;
|
||||
}
|
||||
p.id = Integer.parseInt(id.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
str.charAt(++i);
|
||||
p.data = parser.parse(str.substring(i));
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// do nothing
|
||||
} catch (JsonParseException e) {
|
||||
return error();
|
||||
}
|
||||
|
||||
logger.info(String.format("decoded %s as %s", str, p));
|
||||
return p;
|
||||
}
|
||||
|
||||
private static Packet error() {
|
||||
return new Packet(ERROR, new JsonPrimitive("parser error"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user