Initial commit
This commit is contained in:
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
*.class
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
|
||||||
|
# Intellij project files
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
target/
|
||||||
|
node_modules/
|
||||||
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
language: java
|
||||||
|
jdk:
|
||||||
|
- openjdk7
|
||||||
|
- oraclejdk7
|
||||||
32
LICENSE
Normal file
32
LICENSE
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
Copyright (c) 2013 Naoyuki Kanezawa
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2011 Guillermo Rauch <guillermo@learnboost.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
8
README.md
Normal file
8
README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Socket.IO-client.java
|
||||||
|
|
||||||
|
This is the Socket.IO 1.0 Client Library for Java.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
69
pom.xml
Normal file
69
pom.xml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.github.nkzawa</groupId>
|
||||||
|
<artifactId>socket.io-client</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype-oss-public</id>
|
||||||
|
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.nkzawa</groupId>
|
||||||
|
<artifactId>engine.io-client</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.11</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>1.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>npm-install</id>
|
||||||
|
<phase>process-test-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>exec</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<workingDirectory>./src/test/resources</workingDirectory>
|
||||||
|
<executable>npm</executable>
|
||||||
|
<arguments>
|
||||||
|
<argument>install</argument>
|
||||||
|
</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/test/java/com/github/nkzawa/socketio/client/IOTest.java
Normal file
137
src/test/java/com/github/nkzawa/socketio/client/IOTest.java
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package com.github.nkzawa.socketio.client;
|
||||||
|
|
||||||
|
import com.github.nkzawa.emitter.Emitter;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class IOTest {
|
||||||
|
|
||||||
|
final static int TIMEOUT = 3000;
|
||||||
|
final static int PORT = 3000;
|
||||||
|
|
||||||
|
private Process serverProcess;
|
||||||
|
private ExecutorService serverService;
|
||||||
|
private Future serverOutout;
|
||||||
|
private Future serverError;
|
||||||
|
private Socket socket;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void startServer() throws IOException, InterruptedException {
|
||||||
|
System.out.println("Starting server ...");
|
||||||
|
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
serverProcess = Runtime.getRuntime().exec(
|
||||||
|
"node src/test/resources/index.js " + PORT, new String[] {"DEBUG=socket.io:*,engine*"});
|
||||||
|
serverService = Executors.newCachedThreadPool();
|
||||||
|
serverOutout = serverService.submit(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(serverProcess.getInputStream()));
|
||||||
|
String line;
|
||||||
|
try {
|
||||||
|
line = reader.readLine();
|
||||||
|
latch.countDown();
|
||||||
|
do {
|
||||||
|
System.out.println("SERVER OUT: " + line);
|
||||||
|
} while ((line = reader.readLine()) != null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
serverError = serverService.submit(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(serverProcess.getErrorStream()));
|
||||||
|
String line;
|
||||||
|
try {
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
System.err.println("SERVER ERR: " + line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
latch.await(3000, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void stopServer() throws InterruptedException {
|
||||||
|
System.out.println("Stopping server ...");
|
||||||
|
serverProcess.destroy();
|
||||||
|
serverOutout.cancel(false);
|
||||||
|
serverError.cancel(false);
|
||||||
|
serverService.shutdown();
|
||||||
|
serverService.awaitTermination(3000, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = TIMEOUT)
|
||||||
|
public void openAndClose() throws URISyntaxException, InterruptedException {
|
||||||
|
final BlockingQueue<String> events = new LinkedBlockingQueue<String>();
|
||||||
|
|
||||||
|
IO.Options opts = new IO.Options();
|
||||||
|
opts.forceNew = true;
|
||||||
|
socket = IO.socket("http://localhost:" + PORT, opts);
|
||||||
|
socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
|
||||||
|
@Override
|
||||||
|
public void call(Object... objects) {
|
||||||
|
System.out.println("connect:");
|
||||||
|
events.offer("connect");
|
||||||
|
}
|
||||||
|
}).on(Socket.EVENT_DISCONNECT, new Emitter.Listener() {
|
||||||
|
@Override
|
||||||
|
public void call(Object... objects) {
|
||||||
|
System.out.println("disconnect:");
|
||||||
|
events.offer("disconnect");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.connect();
|
||||||
|
|
||||||
|
assertThat(events.take(), is("connect"));
|
||||||
|
socket.disconnect();
|
||||||
|
assertThat(events.take(), is("disconnect"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = TIMEOUT)
|
||||||
|
public void message() throws URISyntaxException, InterruptedException {
|
||||||
|
final BlockingQueue<String> events = new LinkedBlockingQueue<String>();
|
||||||
|
|
||||||
|
IO.Options opts = new IO.Options();
|
||||||
|
opts.forceNew = true;
|
||||||
|
socket = IO.socket("http://localhost:" + PORT, opts);
|
||||||
|
socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
|
||||||
|
@Override
|
||||||
|
public void call(Object... objects) {
|
||||||
|
System.out.println("connect:");
|
||||||
|
socket.send("hi");
|
||||||
|
}
|
||||||
|
}).on(Socket.EVENT_MESSAGE, new Emitter.Listener() {
|
||||||
|
@Override
|
||||||
|
public void call(Object... objects) {
|
||||||
|
System.out.println("message: " + objects);
|
||||||
|
events.offer((String) objects[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.connect();
|
||||||
|
|
||||||
|
assertThat(events.take(), is("hello client"));
|
||||||
|
assertThat(events.take(), is("hi"));
|
||||||
|
socket.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/test/java/com/github/nkzawa/socketio/client/UrlTest.java
Normal file
51
src/test/java/com/github/nkzawa/socketio/client/UrlTest.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package com.github.nkzawa.socketio.client;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class UrlTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse() throws MalformedURLException, URISyntaxException {
|
||||||
|
URL url = Url.parse(new URI("http://woot.com/test"));
|
||||||
|
assertThat(url.getProtocol(), is("http"));
|
||||||
|
assertThat(url.getHost(), is("woot.com"));
|
||||||
|
assertThat(url.getPath(), is("/test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse_NoProtocol() throws MalformedURLException, URISyntaxException {
|
||||||
|
URL url = Url.parse(new URI("//localhost:3000"));
|
||||||
|
assertThat(url.getProtocol(), is("https"));
|
||||||
|
assertThat(url.getHost(), is("localhost"));
|
||||||
|
assertThat(url.getAuthority(), is("localhost:3000"));
|
||||||
|
assertThat(url.getPort(), is(3000));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse_Namaspace() throws MalformedURLException, URISyntaxException {
|
||||||
|
assertThat(Url.parse(new URI("http://woot.com/woot")).getPath(), is("/woot"));
|
||||||
|
assertThat(Url.parse(new URI("http://google.com")).getPath(), is("/"));
|
||||||
|
assertThat(Url.parse(new URI("http://google.com/")).getPath(), is("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractId() throws MalformedURLException, URISyntaxException {
|
||||||
|
String id1 = Url.extractId(new URL("http://google.com:80/"));
|
||||||
|
String id2 = Url.extractId(new URL("http://google.com/"));
|
||||||
|
String id3 = Url.extractId(new URL("https://google.com/"));
|
||||||
|
assertThat(id1, is(id2));
|
||||||
|
assertThat(id1, is(not(id3)));
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/test/resources/index.js
Normal file
29
src/test/resources/index.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
var server = require('http').Server()
|
||||||
|
, io = require('socket.io')(server)
|
||||||
|
, port = parseInt(process.argv[2], 10) || 3000;
|
||||||
|
|
||||||
|
io.on('connection', function(socket) {
|
||||||
|
socket.send('hello client');
|
||||||
|
|
||||||
|
socket.on('message', function(data) {
|
||||||
|
console.log('message:', data);
|
||||||
|
socket.send(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('echo', function(data) {
|
||||||
|
console.log('echo:', data);
|
||||||
|
socket.emit('echoBack', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function() {
|
||||||
|
console.log('disconnect');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', function() {
|
||||||
|
console.log('error: ' + arguments);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port, function() {
|
||||||
|
console.log('Socket.IO server listening on port', port);
|
||||||
|
});
|
||||||
8
src/test/resources/package.json
Normal file
8
src/test/resources/package.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "socket.io-client.java-test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"socket.io": "nkzawa/socket.io"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user