diff --git a/engine.io-server-test/pom.xml b/engine.io-server-test/pom.xml
index db22fbf..ec2146b 100644
--- a/engine.io-server-test/pom.xml
+++ b/engine.io-server-test/pom.xml
@@ -42,6 +42,18 @@
9.4.10.v20180503
test
+
+ org.seleniumhq.selenium
+ selenium-server
+ 2.52.0
+ test
+
+
+ org.seleniumhq.selenium
+ selenium-htmlunit-driver
+ 2.52.0
+ test
+
io.socket
diff --git a/engine.io-server-test/src/test/java/io/socket/engineio/server/PollingJsonpTest.java b/engine.io-server-test/src/test/java/io/socket/engineio/server/PollingJsonpTest.java
new file mode 100644
index 0000000..739a4ba
--- /dev/null
+++ b/engine.io-server-test/src/test/java/io/socket/engineio/server/PollingJsonpTest.java
@@ -0,0 +1,121 @@
+package io.socket.engineio.server;
+
+import io.socket.engineio.parser.Packet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.jetty.log.LogFactory;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.htmlunit.HtmlUnitDriver;
+import org.openqa.selenium.remote.server.SeleniumServer;
+import org.openqa.selenium.support.events.AbstractWebDriverEventListener;
+import org.openqa.selenium.support.events.EventFiringWebDriver;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Level;
+
+import static org.junit.Assert.*;
+
+public final class PollingJsonpTest {
+
+ private static SeleniumServer sSeleniumServer;
+
+ @Before
+ public void setup() {
+ LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
+
+ java.util.logging.Logger.getLogger("com.gargoylesoftware.htmlunit").setLevel(Level.OFF);
+ java.util.logging.Logger.getLogger("org.apache.commons.httpclient").setLevel(Level.OFF);
+
+ sSeleniumServer = new SeleniumServer(4444);
+ sSeleniumServer.boot();
+ }
+
+ @After
+ public void teardown() {
+ sSeleniumServer.stop();
+ }
+
+ @Test
+ public void echoTest_string() throws Exception {
+ final ServerWrapper serverWrapper = new ServerWrapper();
+
+ try {
+ serverWrapper.startServer();
+ serverWrapper.getEngineIoServer().on("connection", args -> {
+ final EngineIoSocket socket = (EngineIoSocket) args[0];
+ socket.on("message", args1 -> {
+ Packet packet = new Packet(Packet.MESSAGE);
+ packet.data = args1[0];
+ socket.send(packet);
+ });
+ });
+
+ assertEquals(0, executeScriptInBrowser(serverWrapper, "src/test/resources/testPollingJsonp_echo_string.js"));
+ } finally {
+ serverWrapper.stopServer();
+ }
+ }
+
+ @Test
+ public void reverseEchoTest() throws Exception {
+ final ServerWrapper serverWrapper = new ServerWrapper();
+ try {
+ serverWrapper.startServer();
+ serverWrapper.getEngineIoServer().on("connection", args -> {
+ final EngineIoSocket socket = (EngineIoSocket) args[0];
+ final String echoMessage = PollingTest.class.getSimpleName() + System.currentTimeMillis();
+ socket.on("message", args1 -> assertEquals(echoMessage, args1[0]));
+
+ Packet packet = new Packet(Packet.MESSAGE);
+ packet.data = echoMessage;
+ socket.send(packet);
+ });
+
+ assertEquals(0, executeScriptInBrowser(serverWrapper, "src/test/resources/testPollingJsonp_reverseEcho.js"));
+ } finally {
+ serverWrapper.stopServer();
+ }
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private int executeScriptInBrowser(ServerWrapper serverWrapper, String script) throws IOException {
+ class ScriptResult {
+ int result = -1;
+ }
+
+ final HtmlUnitDriver webDriver = new HtmlUnitDriver(true);
+ final EventFiringWebDriver eventFiringWebDriver = new EventFiringWebDriver(webDriver);
+ final ScriptResult scriptResult = new ScriptResult();
+
+ try (FileInputStream fis = new FileInputStream(script)) {
+ final byte[] scriptBytes = new byte[fis.available()];
+ fis.read(scriptBytes);
+ final String scriptContent = new String(scriptBytes, StandardCharsets.UTF_8);
+
+ eventFiringWebDriver.get("http://127.0.0.1:" + serverWrapper.getPort() + "/testPollingJsonp.html");
+ eventFiringWebDriver.register(new AbstractWebDriverEventListener() {
+
+ @Override
+ public void afterScript(String script, WebDriver driver) {
+ final String url = eventFiringWebDriver.getCurrentUrl();
+ if (url.startsWith("http://www.example.com/?result=")) {
+ scriptResult.result = Integer.parseInt(url.substring("http://www.example.com/?result=".length()));
+ }
+ }
+ });
+ eventFiringWebDriver.executeScript(scriptContent, serverWrapper.getPort());
+
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException ignore) {
+ }
+
+ return scriptResult.result;
+ } finally {
+ webDriver.close();
+ }
+ }
+}
diff --git a/engine.io-server-test/src/test/java/io/socket/engineio/server/ServerWrapper.java b/engine.io-server-test/src/test/java/io/socket/engineio/server/ServerWrapper.java
index 7bfebe2..fc94731 100644
--- a/engine.io-server-test/src/test/java/io/socket/engineio/server/ServerWrapper.java
+++ b/engine.io-server-test/src/test/java/io/socket/engineio/server/ServerWrapper.java
@@ -1,5 +1,6 @@
package io.socket.engineio.server;
+import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
@@ -14,7 +15,11 @@
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
import java.util.concurrent.atomic.AtomicInteger;
final class ServerWrapper {
@@ -43,6 +48,26 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
mEngineIoServer.handleRequest(request, response);
}
}), "/engine.io/*");
+ servletContextHandler.addServlet(new ServletHolder(new HttpServlet() {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ final String path = request.getPathInfo();
+ final File file = new File("src/test/resources", path);
+ if (file.exists()) {
+ response.setContentType(Files.probeContentType(file.toPath()));
+
+ try (FileInputStream fis = new FileInputStream(file)) {
+ IOUtils.copy(fis, response.getOutputStream());
+ }
+ } else {
+ response.setStatus(404);
+ try (PrintWriter writer = response.getWriter()) {
+ writer.print("Not Found");
+ writer.flush();
+ }
+ }
+ }
+ }), "/*");
try {
WebSocketUpgradeFilter webSocketUpgradeFilter = WebSocketUpgradeFilter.configureContext(servletContextHandler);
diff --git a/engine.io-server-test/src/test/resources/testPollingJsonp.html b/engine.io-server-test/src/test/resources/testPollingJsonp.html
new file mode 100644
index 0000000..a3d3e6c
--- /dev/null
+++ b/engine.io-server-test/src/test/resources/testPollingJsonp.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Polling Test - JSONP
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/engine.io-server-test/src/test/resources/testPollingJsonp_echo_string.js b/engine.io-server-test/src/test/resources/testPollingJsonp_echo_string.js
new file mode 100644
index 0000000..c2d6d21
--- /dev/null
+++ b/engine.io-server-test/src/test/resources/testPollingJsonp_echo_string.js
@@ -0,0 +1,23 @@
+var returnError = function () {
+ window.setResult(1);
+};
+
+var port = arguments[0];
+socket = eio('http://127.0.0.1:' + port, {
+ transports: ['polling'],
+ jsonp: true,
+ forceJSONP: true
+});
+socket.on('open', function () {
+ var echoMessage = "Hello World";
+ socket.on('message', function (message) {
+ if(message === echoMessage) {
+ window.setResult(0);
+ } else {
+ returnError();
+ }
+ });
+ socket.send(echoMessage);
+});
+socket.on('error', returnError);
+setTimeout(returnError, 750);
diff --git a/engine.io-server-test/src/test/resources/testPollingJsonp_reverseEcho.js b/engine.io-server-test/src/test/resources/testPollingJsonp_reverseEcho.js
new file mode 100644
index 0000000..d187f18
--- /dev/null
+++ b/engine.io-server-test/src/test/resources/testPollingJsonp_reverseEcho.js
@@ -0,0 +1,19 @@
+var returnError = function () {
+ window.setResult(1);
+};
+
+var port = arguments[0];
+socket = eio('http://127.0.0.1:' + port, {
+ transports: ['polling'],
+ jsonp: true,
+ forceJSONP: true
+});
+socket.on("message", function (message) {
+ socket.sendPacket("message", message);
+
+ setTimeout(function () {
+ window.setResult(0);
+ }, 100);
+});
+socket.on('error', returnError);
+setTimeout(returnError, 750);
\ No newline at end of file
diff --git a/engine.io-server/src/main/java/io/socket/engineio/server/transport/Polling.java b/engine.io-server/src/main/java/io/socket/engineio/server/transport/Polling.java
index bcef88d..d803fbd 100644
--- a/engine.io-server/src/main/java/io/socket/engineio/server/transport/Polling.java
+++ b/engine.io-server/src/main/java/io/socket/engineio/server/transport/Polling.java
@@ -3,6 +3,9 @@
import io.socket.engineio.parser.Packet;
import io.socket.engineio.parser.ServerParser;
import io.socket.engineio.server.Transport;
+import io.socket.parseqs.ParseQS;
+import org.json.JSONArray;
+import org.json.JSONObject;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
@@ -69,23 +72,41 @@ public synchronized void send(List packets) {
packets.add(new Packet(Packet.CLOSE));
}
- @SuppressWarnings("unchecked")
- final boolean supportsBinary = (!((Map) mRequest.getAttribute("query")).containsKey("b64"));
+ //noinspection unchecked
+ final Map query = (Map) mRequest.getAttribute("query");
+
+ final boolean supportsBinary = !query.containsKey("b64");
+ final boolean jsonp = query.containsKey("j");
if(packets.size() == 0) {
throw new IllegalArgumentException("No packets to send.");
}
ServerParser.encodePayload(packets.toArray(new Packet[0]), supportsBinary, data -> {
- final String contentType = (data instanceof String)? "text/plain; charset=UTF-8" : "application/octet-stream";
- final int contentLength = (data instanceof String)? ((String) data).length() : ((byte[]) data).length;
+ final String contentType;
+ final byte[] contentBytes;
+
+ if (jsonp) {
+ final String jsonpIndex = query.get("j").replaceAll("[^0-9]", "");
+ final String jsonContentString = (data instanceof String)?
+ JSONObject.quote((String)data) :
+ JSONObject.valueToString(new JSONArray(data));
+ final String jsContentString = jsonContentString
+ .replace("\u2028", "\\u2028")
+ .replace("\u2029", "\\u2029");
+ final String contentString = "___eio[" + jsonpIndex + "](" + jsContentString + ")";
+
+ contentType = "text/javascript; charset=UTF-8";
+ contentBytes = contentString.getBytes(StandardCharsets.UTF_8);
+ } else {
+ contentType = (data instanceof String)? "text/plain; charset=UTF-8" : "application/octet-stream";
+ contentBytes = (data instanceof String)? ((String)data).getBytes(StandardCharsets.UTF_8) : ((byte[])data);
+ }
mResponse.setContentType(contentType);
- mResponse.setContentLength(contentLength);
+ mResponse.setContentLength(contentBytes.length);
try (OutputStream outputStream = mResponse.getOutputStream()) {
- // TODO: Support JSONP
- byte[] writeBytes = (data instanceof String)? ((String) data).getBytes(StandardCharsets.UTF_8) : ((byte[]) data);
- outputStream.write(writeBytes);
+ outputStream.write(contentBytes);
} catch (IOException ex) {
onError("write failure", ex.getMessage());
}
@@ -148,7 +169,11 @@ private void onPollRequest(@SuppressWarnings("unused") HttpServletRequest reques
}
private void onDataRequest(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
+ //noinspection unchecked
+ final Map query = (Map) mRequest.getAttribute("query");
+
final boolean isBinary = request.getContentType().equals("application/octet-stream");
+ final boolean jsonp = query.containsKey("j");
try(final ServletInputStream inputStream = request.getInputStream()) {
final byte[] mReadBuffer = new byte[request.getContentLength()];
@@ -156,7 +181,13 @@ private void onDataRequest(final HttpServletRequest request, final HttpServletRe
//noinspection ResultOfMethodCallIgnored
inputStream.read(mReadBuffer, 0, mReadBuffer.length);
- onData((isBinary)? mReadBuffer : (new String(mReadBuffer, StandardCharsets.UTF_8)));
+ if (jsonp) {
+ final String packetPayloadRaw = ParseQS.decode(new String(mReadBuffer, StandardCharsets.UTF_8)).get("d");
+ final String packetPayload = packetPayloadRaw.replace("\\n", "\n");
+ onData(packetPayload);
+ } else {
+ onData((isBinary)? mReadBuffer : (new String(mReadBuffer, StandardCharsets.UTF_8)));
+ }
}
response.setContentType("text/html");
diff --git a/engine.io-server/src/test/java/io/socket/engineio/server/transport/PollingTest.java b/engine.io-server/src/test/java/io/socket/engineio/server/transport/PollingTest.java
index ab8372c..016b093 100644
--- a/engine.io-server/src/test/java/io/socket/engineio/server/transport/PollingTest.java
+++ b/engine.io-server/src/test/java/io/socket/engineio/server/transport/PollingTest.java
@@ -1,5 +1,6 @@
package io.socket.engineio.server.transport;
+import io.socket.emitter.Emitter;
import io.socket.engineio.parser.Packet;
import io.socket.engineio.parser.ServerParser;
import io.socket.engineio.server.HttpServletResponseImpl;
@@ -11,11 +12,12 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.HashMap;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.*;
+@SuppressWarnings("unchecked")
public final class PollingTest {
@Test
@@ -65,15 +67,64 @@ public void testOnRequest_poll() throws IOException {
final HttpServletResponseImpl response = new HttpServletResponseImpl();
+ final Emitter.Listener drainListener = Mockito.spy(Emitter.Listener.class);
+ Mockito.doAnswer(invocation -> {
+ polling.send(new ArrayList(){{
+ add(new Packet(Packet.MESSAGE, "Test Data"));
+ }});
+ return null;
+ }).when(drainListener).call();
+ polling.on("drain", drainListener);
+
polling.onRequest(request, response);
- Mockito.verify(polling, Mockito.times(1))
- .emit(Mockito.eq("drain"));
+ Mockito.verify(drainListener, Mockito.times(1)).call();
final String responseString = new String(response.getByteOutputStream().toByteArray(), StandardCharsets.UTF_8);
ServerParser.decodePayload(responseString, (packet, index, total) -> {
assertEquals(1, total);
- assertEquals(Packet.NOOP, packet.type);
+ assertEquals(Packet.MESSAGE, packet.type);
+ assertEquals("Test Data", packet.data);
+ return true;
+ });
+ }
+
+ @Test
+ public void testOnRequest_poll_jsonp() throws IOException {
+ final Polling polling = Mockito.spy(new Polling());
+
+ final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ Mockito.doAnswer(invocationOnMock -> "GET").when(request).getMethod();
+ Mockito.doAnswer(invocationOnMock -> {
+ final HashMap queryMap = new HashMap<>();
+ queryMap.put("transport", Polling.NAME);
+ queryMap.put("j", "100");
+ return queryMap;
+ }).when(request).getAttribute("query");
+
+ final HttpServletResponseImpl response = new HttpServletResponseImpl();
+
+ final Emitter.Listener drainListener = Mockito.spy(Emitter.Listener.class);
+ Mockito.doAnswer(invocation -> {
+ polling.send(new ArrayList(){{
+ add(new Packet(Packet.MESSAGE, "Test Data"));
+ }});
+ return null;
+ }).when(drainListener).call();
+ polling.on("drain", drainListener);
+
+ polling.onRequest(request, response);
+
+ Mockito.verify(drainListener, Mockito.times(1)).call();
+
+ final String responseString = new String(response.getByteOutputStream().toByteArray(), StandardCharsets.UTF_8);
+ assertTrue(responseString.startsWith("___eio[100]("));
+
+ final String payloadString = responseString.substring("___eio[100](".length() + 1, responseString.length() - 2);
+ ServerParser.decodePayload(payloadString, (packet, index, total) -> {
+ assertEquals(1, total);
+ assertEquals(Packet.MESSAGE, packet.type);
+ assertEquals("Test Data", packet.data);
return true;
});
}
@@ -118,6 +169,45 @@ public void testOnRequest_data() {
});
}
+ @Test
+ public void testOnRequest_data_jsonp() {
+ final String messageData = "Test Data";
+
+ final Polling polling = Mockito.spy(new Polling());
+ polling.on("packet", args -> {
+ final Packet packet = (Packet) args[0];
+ assertEquals(Packet.MESSAGE, packet.type);
+ assertEquals(messageData, packet.data);
+ });
+
+ final byte[] data = "d=10:4Test Data".getBytes(StandardCharsets.UTF_8);
+ final ByteArrayInputStream requestInputStream = new ByteArrayInputStream(data);
+ final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ Mockito.doAnswer(invocationOnMock -> "POST").when(request).getMethod();
+ Mockito.doAnswer(invocationOnMock -> {
+ final HashMap queryMap = new HashMap<>();
+ queryMap.put("transport", Polling.NAME);
+ queryMap.put("j", "100");
+ return queryMap;
+ }).when(request).getAttribute("query");
+ Mockito.doAnswer(invocationOnMock -> "application/octet-stream").when(request).getContentType();
+ Mockito.doAnswer(invocationOnMock -> data.length).when(request).getContentLength();
+ try {
+ Mockito.doAnswer(invocationOnMock -> new ServletInputStreamWrapper(requestInputStream)).when(request).getInputStream();
+ } catch (IOException ignore) {
+ }
+
+ final HttpServletResponseImpl response = new HttpServletResponseImpl();
+
+ try {
+ polling.onRequest(request, response);
+ } catch (IOException ignore) {
+ }
+
+ Mockito.verify(polling, Mockito.times(1))
+ .emit(Mockito.eq("packet"), Mockito.any(Packet.class));
+ }
+
@Test
public void testClose_client() {
final Polling polling = Mockito.spy(new Polling());