Skip to content

Commit 96b408d

Browse files
committed
Added jsonp support to polling transport
Added integration tests for jsonp
1 parent 95bca58 commit 96b408d

8 files changed

Lines changed: 352 additions & 14 deletions

File tree

engine.io-server-test/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@
4242
<version>9.4.10.v20180503</version>
4343
<scope>test</scope>
4444
</dependency>
45+
<dependency>
46+
<groupId>org.seleniumhq.selenium</groupId>
47+
<artifactId>selenium-server</artifactId>
48+
<version>2.52.0</version>
49+
<scope>test</scope>
50+
</dependency>
51+
<dependency>
52+
<groupId>org.seleniumhq.selenium</groupId>
53+
<artifactId>selenium-htmlunit-driver</artifactId>
54+
<version>2.52.0</version>
55+
<scope>test</scope>
56+
</dependency>
4557

4658
<dependency>
4759
<groupId>io.socket</groupId>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package io.socket.engineio.server;
2+
3+
import io.socket.engineio.parser.Packet;
4+
import org.junit.After;
5+
import org.junit.Before;
6+
import org.junit.Test;
7+
import org.openqa.jetty.log.LogFactory;
8+
import org.openqa.selenium.WebDriver;
9+
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
10+
import org.openqa.selenium.remote.server.SeleniumServer;
11+
import org.openqa.selenium.support.events.AbstractWebDriverEventListener;
12+
import org.openqa.selenium.support.events.EventFiringWebDriver;
13+
14+
import java.io.FileInputStream;
15+
import java.io.IOException;
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.logging.Level;
18+
19+
import static org.junit.Assert.*;
20+
21+
public final class PollingJsonpTest {
22+
23+
private static SeleniumServer sSeleniumServer;
24+
25+
@Before
26+
public void setup() {
27+
LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
28+
29+
java.util.logging.Logger.getLogger("com.gargoylesoftware.htmlunit").setLevel(Level.OFF);
30+
java.util.logging.Logger.getLogger("org.apache.commons.httpclient").setLevel(Level.OFF);
31+
32+
sSeleniumServer = new SeleniumServer(4444);
33+
sSeleniumServer.boot();
34+
}
35+
36+
@After
37+
public void teardown() {
38+
sSeleniumServer.stop();
39+
}
40+
41+
@Test
42+
public void echoTest_string() throws Exception {
43+
final ServerWrapper serverWrapper = new ServerWrapper();
44+
45+
try {
46+
serverWrapper.startServer();
47+
serverWrapper.getEngineIoServer().on("connection", args -> {
48+
final EngineIoSocket socket = (EngineIoSocket) args[0];
49+
socket.on("message", args1 -> {
50+
Packet packet = new Packet(Packet.MESSAGE);
51+
packet.data = args1[0];
52+
socket.send(packet);
53+
});
54+
});
55+
56+
assertEquals(0, executeScriptInBrowser(serverWrapper, "src/test/resources/testPollingJsonp_echo_string.js"));
57+
} finally {
58+
serverWrapper.stopServer();
59+
}
60+
}
61+
62+
@Test
63+
public void reverseEchoTest() throws Exception {
64+
final ServerWrapper serverWrapper = new ServerWrapper();
65+
try {
66+
serverWrapper.startServer();
67+
serverWrapper.getEngineIoServer().on("connection", args -> {
68+
final EngineIoSocket socket = (EngineIoSocket) args[0];
69+
final String echoMessage = PollingTest.class.getSimpleName() + System.currentTimeMillis();
70+
socket.on("message", args1 -> assertEquals(echoMessage, args1[0]));
71+
72+
Packet packet = new Packet(Packet.MESSAGE);
73+
packet.data = echoMessage;
74+
socket.send(packet);
75+
});
76+
77+
assertEquals(0, executeScriptInBrowser(serverWrapper, "src/test/resources/testPollingJsonp_reverseEcho.js"));
78+
} finally {
79+
serverWrapper.stopServer();
80+
}
81+
}
82+
83+
@SuppressWarnings("ResultOfMethodCallIgnored")
84+
private int executeScriptInBrowser(ServerWrapper serverWrapper, String script) throws IOException {
85+
class ScriptResult {
86+
int result = -1;
87+
}
88+
89+
final HtmlUnitDriver webDriver = new HtmlUnitDriver(true);
90+
final EventFiringWebDriver eventFiringWebDriver = new EventFiringWebDriver(webDriver);
91+
final ScriptResult scriptResult = new ScriptResult();
92+
93+
try (FileInputStream fis = new FileInputStream(script)) {
94+
final byte[] scriptBytes = new byte[fis.available()];
95+
fis.read(scriptBytes);
96+
final String scriptContent = new String(scriptBytes, StandardCharsets.UTF_8);
97+
98+
eventFiringWebDriver.get("http://127.0.0.1:" + serverWrapper.getPort() + "/testPollingJsonp.html");
99+
eventFiringWebDriver.register(new AbstractWebDriverEventListener() {
100+
101+
@Override
102+
public void afterScript(String script, WebDriver driver) {
103+
final String url = eventFiringWebDriver.getCurrentUrl();
104+
if (url.startsWith("http://www.example.com/?result=")) {
105+
scriptResult.result = Integer.parseInt(url.substring("http://www.example.com/?result=".length()));
106+
}
107+
}
108+
});
109+
eventFiringWebDriver.executeScript(scriptContent, serverWrapper.getPort());
110+
111+
try {
112+
Thread.sleep(3000);
113+
} catch (InterruptedException ignore) {
114+
}
115+
116+
return scriptResult.result;
117+
} finally {
118+
webDriver.close();
119+
}
120+
}
121+
}

engine.io-server-test/src/test/java/io/socket/engineio/server/ServerWrapper.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.socket.engineio.server;
22

3+
import org.apache.commons.io.IOUtils;
34
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
45
import org.eclipse.jetty.server.Handler;
56
import org.eclipse.jetty.server.Server;
@@ -14,7 +15,11 @@
1415
import javax.servlet.http.HttpServlet;
1516
import javax.servlet.http.HttpServletRequest;
1617
import javax.servlet.http.HttpServletResponse;
18+
import java.io.File;
19+
import java.io.FileInputStream;
1720
import java.io.IOException;
21+
import java.io.PrintWriter;
22+
import java.nio.file.Files;
1823
import java.util.concurrent.atomic.AtomicInteger;
1924

2025
final class ServerWrapper {
@@ -43,6 +48,26 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
4348
mEngineIoServer.handleRequest(request, response);
4449
}
4550
}), "/engine.io/*");
51+
servletContextHandler.addServlet(new ServletHolder(new HttpServlet() {
52+
@Override
53+
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
54+
final String path = request.getPathInfo();
55+
final File file = new File("src/test/resources", path);
56+
if (file.exists()) {
57+
response.setContentType(Files.probeContentType(file.toPath()));
58+
59+
try (FileInputStream fis = new FileInputStream(file)) {
60+
IOUtils.copy(fis, response.getOutputStream());
61+
}
62+
} else {
63+
response.setStatus(404);
64+
try (PrintWriter writer = response.getWriter()) {
65+
writer.print("Not Found");
66+
writer.flush();
67+
}
68+
}
69+
}
70+
}), "/*");
4671

4772
try {
4873
WebSocketUpgradeFilter webSocketUpgradeFilter = WebSocketUpgradeFilter.configureContext(servletContextHandler);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
6+
<title>Polling Test - JSONP</title>
7+
8+
<script type="text/javascript" src="node_modules/engine.io-client/engine.io.js"></script>
9+
<script type="text/javascript">
10+
window.setResult = function (result) {
11+
window.location = "http://www.example.com/?result=" + result;
12+
};
13+
</script>
14+
</head>
15+
<body>
16+
</body>
17+
</html>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var returnError = function () {
2+
window.setResult(1);
3+
};
4+
5+
var port = arguments[0];
6+
socket = eio('http://127.0.0.1:' + port, {
7+
transports: ['polling'],
8+
jsonp: true,
9+
forceJSONP: true
10+
});
11+
socket.on('open', function () {
12+
var echoMessage = "Hello World";
13+
socket.on('message', function (message) {
14+
if(message === echoMessage) {
15+
window.setResult(0);
16+
} else {
17+
returnError();
18+
}
19+
});
20+
socket.send(echoMessage);
21+
});
22+
socket.on('error', returnError);
23+
setTimeout(returnError, 750);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var returnError = function () {
2+
window.setResult(1);
3+
};
4+
5+
var port = arguments[0];
6+
socket = eio('http://127.0.0.1:' + port, {
7+
transports: ['polling'],
8+
jsonp: true,
9+
forceJSONP: true
10+
});
11+
socket.on("message", function (message) {
12+
socket.sendPacket("message", message);
13+
14+
setTimeout(function () {
15+
window.setResult(0);
16+
}, 100);
17+
});
18+
socket.on('error', returnError);
19+
setTimeout(returnError, 750);

engine.io-server/src/main/java/io/socket/engineio/server/transport/Polling.java

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import io.socket.engineio.parser.Packet;
44
import io.socket.engineio.parser.ServerParser;
55
import io.socket.engineio.server.Transport;
6+
import io.socket.parseqs.ParseQS;
7+
import org.json.JSONArray;
8+
import org.json.JSONObject;
69

710
import javax.servlet.ServletInputStream;
811
import javax.servlet.http.HttpServletRequest;
@@ -69,23 +72,41 @@ public synchronized void send(List<Packet> packets) {
6972
packets.add(new Packet(Packet.CLOSE));
7073
}
7174

72-
@SuppressWarnings("unchecked")
73-
final boolean supportsBinary = (!((Map<String, String>) mRequest.getAttribute("query")).containsKey("b64"));
75+
//noinspection unchecked
76+
final Map<String, String> query = (Map<String, String>) mRequest.getAttribute("query");
77+
78+
final boolean supportsBinary = !query.containsKey("b64");
79+
final boolean jsonp = query.containsKey("j");
7480

7581
if(packets.size() == 0) {
7682
throw new IllegalArgumentException("No packets to send.");
7783
}
7884
ServerParser.encodePayload(packets.toArray(new Packet[0]), supportsBinary, data -> {
79-
final String contentType = (data instanceof String)? "text/plain; charset=UTF-8" : "application/octet-stream";
80-
final int contentLength = (data instanceof String)? ((String) data).length() : ((byte[]) data).length;
85+
final String contentType;
86+
final byte[] contentBytes;
87+
88+
if (jsonp) {
89+
final String jsonpIndex = query.get("j").replaceAll("[^0-9]", "");
90+
final String jsonContentString = (data instanceof String)?
91+
JSONObject.quote((String)data) :
92+
JSONObject.valueToString(new JSONArray(data));
93+
final String jsContentString = jsonContentString
94+
.replace("\u2028", "\\u2028")
95+
.replace("\u2029", "\\u2029");
96+
final String contentString = "___eio[" + jsonpIndex + "](" + jsContentString + ")";
97+
98+
contentType = "text/javascript; charset=UTF-8";
99+
contentBytes = contentString.getBytes(StandardCharsets.UTF_8);
100+
} else {
101+
contentType = (data instanceof String)? "text/plain; charset=UTF-8" : "application/octet-stream";
102+
contentBytes = (data instanceof String)? ((String)data).getBytes(StandardCharsets.UTF_8) : ((byte[])data);
103+
}
81104

82105
mResponse.setContentType(contentType);
83-
mResponse.setContentLength(contentLength);
106+
mResponse.setContentLength(contentBytes.length);
84107

85108
try (OutputStream outputStream = mResponse.getOutputStream()) {
86-
// TODO: Support JSONP
87-
byte[] writeBytes = (data instanceof String)? ((String) data).getBytes(StandardCharsets.UTF_8) : ((byte[]) data);
88-
outputStream.write(writeBytes);
109+
outputStream.write(contentBytes);
89110
} catch (IOException ex) {
90111
onError("write failure", ex.getMessage());
91112
}
@@ -148,15 +169,25 @@ private void onPollRequest(@SuppressWarnings("unused") HttpServletRequest reques
148169
}
149170

150171
private void onDataRequest(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
172+
//noinspection unchecked
173+
final Map<String, String> query = (Map<String, String>) mRequest.getAttribute("query");
174+
151175
final boolean isBinary = request.getContentType().equals("application/octet-stream");
176+
final boolean jsonp = query.containsKey("j");
152177

153178
try(final ServletInputStream inputStream = request.getInputStream()) {
154179
final byte[] mReadBuffer = new byte[request.getContentLength()];
155180

156181
//noinspection ResultOfMethodCallIgnored
157182
inputStream.read(mReadBuffer, 0, mReadBuffer.length);
158183

159-
onData((isBinary)? mReadBuffer : (new String(mReadBuffer, StandardCharsets.UTF_8)));
184+
if (jsonp) {
185+
final String packetPayloadRaw = ParseQS.decode(new String(mReadBuffer, StandardCharsets.UTF_8)).get("d");
186+
final String packetPayload = packetPayloadRaw.replace("\\n", "\n");
187+
onData(packetPayload);
188+
} else {
189+
onData((isBinary)? mReadBuffer : (new String(mReadBuffer, StandardCharsets.UTF_8)));
190+
}
160191
}
161192

162193
response.setContentType("text/html");

0 commit comments

Comments
 (0)