Skip to content

Commit

Permalink
Added jsonp support to polling transport
Browse files Browse the repository at this point in the history
Added integration tests for jsonp
  • Loading branch information
trinopoty committed Jan 9, 2019
1 parent 95bca58 commit 96b408d
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 14 deletions.
12 changes: 12 additions & 0 deletions engine.io-server-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@
<version>9.4.10.v20180503</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
<version>2.52.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-htmlunit-driver</artifactId>
<version>2.52.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.socket</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
17 changes: 17 additions & 0 deletions engine.io-server-test/src/test/resources/testPollingJsonp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">

<title>Polling Test - JSONP</title>

<script type="text/javascript" src="node_modules/engine.io-client/engine.io.js"></script>
<script type="text/javascript">
window.setResult = function (result) {
window.location = "http://www.example.com/?result=" + result;
};
</script>
</head>
<body>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,23 +72,41 @@ public synchronized void send(List<Packet> packets) {
packets.add(new Packet(Packet.CLOSE));
}

@SuppressWarnings("unchecked")
final boolean supportsBinary = (!((Map<String, String>) mRequest.getAttribute("query")).containsKey("b64"));
//noinspection unchecked
final Map<String, String> query = (Map<String, String>) 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());
}
Expand Down Expand Up @@ -148,15 +169,25 @@ private void onPollRequest(@SuppressWarnings("unused") HttpServletRequest reques
}

private void onDataRequest(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
//noinspection unchecked
final Map<String, String> query = (Map<String, String>) 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()];

//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");
Expand Down
Loading

0 comments on commit 96b408d

Please sign in to comment.