mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Allow to delegate requests to dev server to another server
This commit is contained in:
parent
c2697dce88
commit
97a1db1b79
10
pom.xml
10
pom.xml
|
@ -177,6 +177,16 @@
|
|||
<artifactId>javax-websocket-server-impl</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.teavm.tooling.ConsoleTeaVMToolLog;
|
|||
|
||||
public final class TeaVMDevServerRunner {
|
||||
private static Options options = new Options();
|
||||
private ConsoleTeaVMToolLog log = new ConsoleTeaVMToolLog(false);
|
||||
private DevServer devServer;
|
||||
private CommandLine commandLine;
|
||||
|
||||
|
@ -71,7 +70,7 @@ public final class TeaVMDevServerRunner {
|
|||
options.addOption(OptionBuilder
|
||||
.withDescription("display indicator on web page")
|
||||
.withLongOpt("indicator")
|
||||
.create());
|
||||
.create('i'));
|
||||
options.addOption(OptionBuilder
|
||||
.withDescription("automatically reload page when compilation completes")
|
||||
.withLongOpt("auto-reload")
|
||||
|
@ -80,6 +79,18 @@ public final class TeaVMDevServerRunner {
|
|||
.withDescription("display more messages on server log")
|
||||
.withLongOpt("verbose")
|
||||
.create('v'));
|
||||
options.addOption(OptionBuilder
|
||||
.withArgName("URL")
|
||||
.hasArg()
|
||||
.withDescription("delegate requests to URL")
|
||||
.withLongOpt("proxy-url")
|
||||
.create());
|
||||
options.addOption(OptionBuilder
|
||||
.withArgName("path")
|
||||
.hasArg()
|
||||
.withDescription("delegate requests from path")
|
||||
.withLongOpt("proxy-path")
|
||||
.create());
|
||||
}
|
||||
|
||||
private TeaVMDevServerRunner(CommandLine commandLine) {
|
||||
|
@ -103,7 +114,6 @@ public final class TeaVMDevServerRunner {
|
|||
|
||||
TeaVMDevServerRunner runner = new TeaVMDevServerRunner(commandLine);
|
||||
runner.parseArguments();
|
||||
runner.setUp();
|
||||
runner.runAll();
|
||||
}
|
||||
|
||||
|
@ -124,6 +134,13 @@ public final class TeaVMDevServerRunner {
|
|||
}
|
||||
}
|
||||
|
||||
if (commandLine.hasOption("proxy-url")) {
|
||||
devServer.setProxyUrl(commandLine.getOptionValue("proxy-url"));
|
||||
}
|
||||
if (commandLine.hasOption("proxy-path")) {
|
||||
devServer.setProxyPath(commandLine.getOptionValue("proxy-path"));
|
||||
}
|
||||
|
||||
String[] args = commandLine.getArgs();
|
||||
if (args.length != 1) {
|
||||
System.err.println("Unexpected arguments");
|
||||
|
@ -154,10 +171,6 @@ public final class TeaVMDevServerRunner {
|
|||
}
|
||||
}
|
||||
|
||||
private void setUp() {
|
||||
devServer.setLog(log);
|
||||
}
|
||||
|
||||
private void runAll() {
|
||||
devServer.start();
|
||||
}
|
||||
|
|
|
@ -75,6 +75,10 @@
|
|||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>javax-websocket-server-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
|
@ -86,6 +90,12 @@
|
|||
<artifactId>jackson-databind</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -25,13 +25,19 @@ import java.io.OutputStreamWriter;
|
|||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -40,11 +46,26 @@ import java.util.function.Supplier;
|
|||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.eclipse.jetty.websocket.client.io.UpgradeListener;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
import org.teavm.backend.javascript.JavaScriptTarget;
|
||||
import org.teavm.cache.InMemoryMethodNodeCache;
|
||||
import org.teavm.cache.InMemoryProgramCache;
|
||||
|
@ -69,17 +90,25 @@ import org.teavm.vm.TeaVMProgressListener;
|
|||
|
||||
public class CodeServlet extends HttpServlet {
|
||||
private static final Supplier<InputStream> EMPTY_CONTENT = () -> null;
|
||||
private WebSocketServletFactory wsFactory;
|
||||
|
||||
private String mainClass;
|
||||
private String[] classPath;
|
||||
private String fileName = "classes.js";
|
||||
private String pathToFile = "/";
|
||||
private String indicatorWsPath;
|
||||
private List<String> sourcePath = new ArrayList<>();
|
||||
private TeaVMToolLog log = new EmptyTeaVMToolLog();
|
||||
private boolean indicator;
|
||||
private boolean automaticallyReloaded;
|
||||
private int port;
|
||||
private int debugPort;
|
||||
private String proxyUrl;
|
||||
private String proxyPath = "/";
|
||||
private String proxyHost;
|
||||
private String proxyProtocol;
|
||||
private int proxyPort;
|
||||
private String proxyBaseUrl;
|
||||
|
||||
private Map<String, Supplier<InputStream>> sourceFileCache = new HashMap<>();
|
||||
|
||||
|
@ -95,7 +124,7 @@ public class CodeServlet extends HttpServlet {
|
|||
private final Map<String, byte[]> content = new HashMap<>();
|
||||
private MemoryBuildTarget buildTarget = new MemoryBuildTarget();
|
||||
|
||||
private final Set<CodeWsEndpoint> wsEndpoints = new LinkedHashSet<>();
|
||||
private final Set<ProgressHandler> progressHandlers = new LinkedHashSet<>();
|
||||
private final Object statusLock = new Object();
|
||||
private volatile boolean cancelRequested;
|
||||
private boolean compiling;
|
||||
|
@ -103,10 +132,15 @@ public class CodeServlet extends HttpServlet {
|
|||
private boolean waiting;
|
||||
private Thread buildThread;
|
||||
private List<DevServerListener> listeners = new ArrayList<>();
|
||||
private HttpClient httpClient;
|
||||
private WebSocketClient wsClient = new WebSocketClient();
|
||||
|
||||
public CodeServlet(String mainClass, String[] classPath) {
|
||||
this.mainClass = mainClass;
|
||||
this.classPath = classPath.clone();
|
||||
|
||||
httpClient = new HttpClient();
|
||||
httpClient.setFollowRedirects(false);
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
|
@ -114,13 +148,7 @@ public class CodeServlet extends HttpServlet {
|
|||
}
|
||||
|
||||
public void setPathToFile(String pathToFile) {
|
||||
if (!pathToFile.endsWith("/")) {
|
||||
pathToFile += "/";
|
||||
}
|
||||
if (!pathToFile.startsWith("/")) {
|
||||
pathToFile = "/" + pathToFile;
|
||||
}
|
||||
this.pathToFile = pathToFile;
|
||||
this.pathToFile = normalizePath(pathToFile);
|
||||
}
|
||||
|
||||
public List<String> getSourcePath() {
|
||||
|
@ -147,9 +175,17 @@ public class CodeServlet extends HttpServlet {
|
|||
this.automaticallyReloaded = automaticallyReloaded;
|
||||
}
|
||||
|
||||
public void addWsEndpoint(CodeWsEndpoint endpoint) {
|
||||
synchronized (wsEndpoints) {
|
||||
wsEndpoints.add(endpoint);
|
||||
public void setProxyUrl(String proxyUrl) {
|
||||
this.proxyUrl = proxyUrl;
|
||||
}
|
||||
|
||||
public void setProxyPath(String proxyPath) {
|
||||
this.proxyPath = normalizePath(proxyPath);
|
||||
}
|
||||
|
||||
public void addProgressHandler(ProgressHandler handler) {
|
||||
synchronized (progressHandlers) {
|
||||
progressHandlers.add(handler);
|
||||
}
|
||||
|
||||
double progress;
|
||||
|
@ -160,12 +196,12 @@ public class CodeServlet extends HttpServlet {
|
|||
progress = this.progress;
|
||||
}
|
||||
|
||||
endpoint.progress(progress);
|
||||
handler.progress(progress);
|
||||
}
|
||||
|
||||
public void removeWsEndpoint(CodeWsEndpoint endpoint) {
|
||||
synchronized (wsEndpoints) {
|
||||
wsEndpoints.remove(endpoint);
|
||||
public void removeProgressHandler(ProgressHandler handler) {
|
||||
synchronized (progressHandlers) {
|
||||
progressHandlers.remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,20 +237,73 @@ public class CodeServlet extends HttpServlet {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
|
||||
if (proxyUrl != null) {
|
||||
try {
|
||||
httpClient.start();
|
||||
wsClient.start();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
URL url = new URL(proxyUrl);
|
||||
proxyPort = url.getPort();
|
||||
proxyHost = proxyPort != 80 ? url.getHost() + ":" + proxyPort : url.getHost();
|
||||
proxyProtocol = url.getProtocol();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(proxyProtocol).append("://").append(proxyHost);
|
||||
proxyBaseUrl = sb.toString();
|
||||
} catch (MalformedURLException e) {
|
||||
log.warning("Could not extract host from URL: " + proxyUrl, e);
|
||||
}
|
||||
}
|
||||
|
||||
indicatorWsPath = pathToFile + fileName + ".ws";
|
||||
WebSocketPolicy wsPolicy = new WebSocketPolicy(WebSocketBehavior.SERVER);
|
||||
wsFactory = WebSocketServletFactory.Loader.load(config.getServletContext(), wsPolicy);
|
||||
wsFactory.setCreator((req, resp) -> {
|
||||
ProxyWsClient proxyClient = (ProxyWsClient) req.getHttpServletRequest().getAttribute("teavm.ws.client");
|
||||
if (proxyClient == null) {
|
||||
return new CodeWsEndpoint(this);
|
||||
} else {
|
||||
ProxyWsClient proxy = new ProxyWsClient();
|
||||
proxy.setTarget(proxyClient);
|
||||
proxyClient.setTarget(proxy);
|
||||
return proxy;
|
||||
}
|
||||
});
|
||||
try {
|
||||
wsFactory.start();
|
||||
} catch (Exception e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
String path = req.getPathInfo();
|
||||
if (path != null) {
|
||||
log.debug("Serving " + path);
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
if (path.startsWith(pathToFile) && path.length() > pathToFile.length()) {
|
||||
if (req.getMethod().equals("GET") && path.startsWith(pathToFile) && path.length() > pathToFile.length()) {
|
||||
String fileName = path.substring(pathToFile.length());
|
||||
if (fileName.startsWith("src/")) {
|
||||
if (serveSourceFile(fileName.substring("src/".length()), resp)) {
|
||||
log.debug("File " + path + " served as source file");
|
||||
return;
|
||||
}
|
||||
} else if (path.equals(indicatorWsPath)) {
|
||||
if (wsFactory.isUpgradeRequest(req, resp)) {
|
||||
if (wsFactory.acceptWebSocket(req, resp) || resp.isCommitted()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
byte[] fileContent;
|
||||
boolean firstTime;
|
||||
|
@ -236,15 +325,216 @@ public class CodeServlet extends HttpServlet {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (proxyUrl != null && path.startsWith(proxyPath)) {
|
||||
if (wsFactory.isUpgradeRequest(req, resp)) {
|
||||
proxyWebSocket(req, resp, path);
|
||||
} else {
|
||||
proxy(req, resp, path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("File " + path + " not found");
|
||||
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
|
||||
private void proxy(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
|
||||
AsyncContext async = req.startAsync();
|
||||
|
||||
String relPath = path.substring(proxyPath.length());
|
||||
StringBuilder sb = new StringBuilder(proxyUrl);
|
||||
if (!relPath.isEmpty() && !proxyUrl.endsWith("/")) {
|
||||
sb.append("/");
|
||||
}
|
||||
sb.append(relPath);
|
||||
|
||||
if (req.getQueryString() != null) {
|
||||
sb.append("?").append(req.getQueryString());
|
||||
}
|
||||
log.debug("Trying to serve '" + relPath + "' from '" + sb + "'");
|
||||
|
||||
Request proxyReq = httpClient.newRequest(sb.toString());
|
||||
proxyReq.method(req.getMethod());
|
||||
copyRequestHeaders(req, proxyReq::header);
|
||||
|
||||
proxyReq.content(new InputStreamContentProvider(req.getInputStream()));
|
||||
HeaderSender headerSender = new HeaderSender(resp);
|
||||
|
||||
proxyReq.onResponseContent((response, responseContent) -> {
|
||||
headerSender.send(response);
|
||||
try {
|
||||
WritableByteChannel channel = Channels.newChannel(resp.getOutputStream());
|
||||
while (responseContent.remaining() > 0) {
|
||||
channel.write(responseContent);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
proxyReq.send(result -> {
|
||||
headerSender.send(result.getResponse());
|
||||
async.complete();
|
||||
});
|
||||
}
|
||||
|
||||
class HeaderSender {
|
||||
final HttpServletResponse resp;
|
||||
boolean sent;
|
||||
|
||||
HeaderSender(HttpServletResponse resp) {
|
||||
this.resp = resp;
|
||||
}
|
||||
|
||||
void send(Response response) {
|
||||
if (sent) {
|
||||
return;
|
||||
}
|
||||
|
||||
sent = true;
|
||||
resp.setStatus(response.getStatus());
|
||||
|
||||
for (HttpField field : response.getHeaders()) {
|
||||
if (field.getName().toLowerCase().equals("location")) {
|
||||
String value = field.getValue();
|
||||
if (value.startsWith(proxyUrl)) {
|
||||
String relLocation = value.substring(proxyUrl.length());
|
||||
resp.addHeader(field.getName(), "http://localhost:" + port + proxyPath + relLocation);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
resp.addHeader(field.getName(), field.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void proxyWebSocket(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
|
||||
AsyncContext async = req.startAsync();
|
||||
|
||||
String relPath = path.substring(proxyPath.length());
|
||||
StringBuilder sb = new StringBuilder(proxyProtocol.equals("http") ? "ws" : "wss").append("://");
|
||||
sb.append(proxyHost);
|
||||
if (!relPath.isEmpty()) {
|
||||
sb.append("/");
|
||||
}
|
||||
sb.append(relPath);
|
||||
if (req.getQueryString() != null) {
|
||||
sb.append("?").append(req.getQueryString());
|
||||
}
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(sb.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
ProxyWsClient client = new ProxyWsClient();
|
||||
req.setAttribute("teavm.ws.client", client);
|
||||
ClientUpgradeRequest proxyReq = new ClientUpgradeRequest();
|
||||
proxyReq.setMethod(req.getMethod());
|
||||
Map<String, List<String>> headers = new LinkedHashMap<>();
|
||||
copyRequestHeaders(req, (key, value) -> headers.computeIfAbsent(key, k -> new ArrayList<>()).add(value));
|
||||
proxyReq.setHeaders(headers);
|
||||
|
||||
wsClient.connect(client, uri, proxyReq, new UpgradeListener() {
|
||||
@Override
|
||||
public void onHandshakeRequest(UpgradeRequest request) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHandshakeResponse(UpgradeResponse response) {
|
||||
resp.setStatus(response.getStatusCode());
|
||||
for (String header : response.getHeaderNames()) {
|
||||
switch (header.toLowerCase()) {
|
||||
case "connection":
|
||||
case "date":
|
||||
case "sec-websocket-accept":
|
||||
case "upgrade":
|
||||
continue;
|
||||
}
|
||||
for (String value : response.getHeaders(header)) {
|
||||
resp.addHeader(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
wsFactory.acceptWebSocket(req, resp);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
async.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void copyRequestHeaders(HttpServletRequest req, HeaderConsumer proxyReq) {
|
||||
Enumeration<String> headers = req.getHeaderNames();
|
||||
while (headers.hasMoreElements()) {
|
||||
String header = headers.nextElement();
|
||||
String headerLower = header.toLowerCase();
|
||||
switch (headerLower) {
|
||||
case "host":
|
||||
if (proxyHost != null) {
|
||||
proxyReq.header(header, proxyHost);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case "origin":
|
||||
if (proxyBaseUrl != null) {
|
||||
String origin = req.getHeader(header);
|
||||
if (origin.equals("http://localhost:" + port)) {
|
||||
proxyReq.header(header, proxyBaseUrl);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "referer": {
|
||||
String referer = req.getHeader(header);
|
||||
String localUrl = "http://localhost:" + port + "/";
|
||||
if (referer.startsWith(localUrl)) {
|
||||
String relReferer = referer.substring(localUrl.length());
|
||||
proxyReq.header(header, proxyUrl + relReferer);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "connection":
|
||||
case "upgrade":
|
||||
case "user-agent":
|
||||
case "sec-websocket-key":
|
||||
case "sec-websocket-version":
|
||||
case "sec-websocket-extensions":
|
||||
case "accept-encoding":
|
||||
continue;
|
||||
}
|
||||
Enumeration<String> values = req.getHeaders(header);
|
||||
while (values.hasMoreElements()) {
|
||||
proxyReq.header(header, values.nextElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
try {
|
||||
wsFactory.stop();
|
||||
} catch (Exception e) {
|
||||
log.warning("Error stopping WebSocket server", e);
|
||||
}
|
||||
if (proxyUrl != null) {
|
||||
try {
|
||||
httpClient.stop();
|
||||
} catch (Exception e) {
|
||||
log.warning("Error stopping HTTP client", e);
|
||||
}
|
||||
try {
|
||||
wsClient.stop();
|
||||
} catch (Exception e) {
|
||||
log.warning("Error stopping WebSocket client", e);
|
||||
}
|
||||
}
|
||||
stopped = true;
|
||||
synchronized (statusLock) {
|
||||
if (waiting) {
|
||||
|
@ -602,13 +892,13 @@ public class CodeServlet extends HttpServlet {
|
|||
this.progress = progress;
|
||||
}
|
||||
|
||||
CodeWsEndpoint[] endpoints;
|
||||
synchronized (wsEndpoints) {
|
||||
endpoints = wsEndpoints.toArray(new CodeWsEndpoint[0]);
|
||||
ProgressHandler[] handlers;
|
||||
synchronized (progressHandlers) {
|
||||
handlers = progressHandlers.toArray(new ProgressHandler[0]);
|
||||
}
|
||||
|
||||
for (CodeWsEndpoint endpoint : endpoints) {
|
||||
endpoint.progress(progress);
|
||||
for (ProgressHandler handler : handlers) {
|
||||
handler.progress(progress);
|
||||
}
|
||||
|
||||
for (DevServerListener listener : listeners) {
|
||||
|
@ -624,13 +914,13 @@ public class CodeServlet extends HttpServlet {
|
|||
compiling = false;
|
||||
}
|
||||
|
||||
CodeWsEndpoint[] endpoints;
|
||||
synchronized (wsEndpoints) {
|
||||
endpoints = wsEndpoints.toArray(new CodeWsEndpoint[0]);
|
||||
ProgressHandler[] handlers;
|
||||
synchronized (progressHandlers) {
|
||||
handlers = progressHandlers.toArray(new ProgressHandler[0]);
|
||||
}
|
||||
|
||||
for (CodeWsEndpoint endpoint : endpoints) {
|
||||
endpoint.complete(success);
|
||||
for (ProgressHandler handler : handlers) {
|
||||
handler.complete(success);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -725,4 +1015,14 @@ public class CodeServlet extends HttpServlet {
|
|||
return TeaVMProgressFeedback.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
static String normalizePath(String path) {
|
||||
if (!path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,43 +15,50 @@
|
|||
*/
|
||||
package org.teavm.devserver;
|
||||
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
|
||||
@ServerEndpoint("/")
|
||||
@WebSocket
|
||||
public class CodeWsEndpoint {
|
||||
private Session session;
|
||||
private Map<Session, ProgressHandlerImpl> progressHandlerMap = new HashMap<>();
|
||||
private CodeServlet servlet;
|
||||
|
||||
@OnOpen
|
||||
public CodeWsEndpoint(CodeServlet servlet) {
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void open(Session session) {
|
||||
ProgressHandlerImpl progressHandler = new ProgressHandlerImpl(session);
|
||||
progressHandlerMap.put(session, progressHandler);
|
||||
servlet.addProgressHandler(progressHandler);
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void close(Session session, int code, String reason) {
|
||||
ProgressHandlerImpl handler = progressHandlerMap.remove(session);
|
||||
servlet.removeProgressHandler(handler);
|
||||
}
|
||||
|
||||
static class ProgressHandlerImpl implements ProgressHandler {
|
||||
Session session;
|
||||
|
||||
ProgressHandlerImpl(Session session) {
|
||||
this.session = session;
|
||||
servlet = (CodeServlet) session.getUserProperties().get("teavm.servlet");
|
||||
if (servlet != null) {
|
||||
servlet.addWsEndpoint(this);
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void close() {
|
||||
if (servlet != null) {
|
||||
servlet.removeWsEndpoint(this);
|
||||
}
|
||||
servlet = null;
|
||||
session = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(double value) {
|
||||
if (session != null) {
|
||||
session.getAsyncRemote().sendText("{ \"command\": \"compiling\", \"progress\": " + value + " }");
|
||||
}
|
||||
session.getRemote().sendStringByFuture("{ \"command\": \"compiling\", \"progress\": " + value + " }");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(boolean success) {
|
||||
if (session != null) {
|
||||
session.getAsyncRemote().sendText("{ \"command\": \"complete\", \"success\": " + success + " }");
|
||||
session.getRemote().sendStringByFuture("{ \"command\": \"complete\", \"success\": " + success + " }");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,20 +16,11 @@
|
|||
package org.teavm.devserver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
|
||||
import org.teavm.tooling.TeaVMToolLog;
|
||||
|
||||
public class DevServer {
|
||||
|
@ -47,6 +38,8 @@ public class DevServer {
|
|||
private Server server;
|
||||
private int port = 9090;
|
||||
private int debugPort;
|
||||
private String proxyUrl;
|
||||
private String proxyPath = "/";
|
||||
|
||||
public void setMainClass(String mainClass) {
|
||||
this.mainClass = mainClass;
|
||||
|
@ -90,6 +83,14 @@ public class DevServer {
|
|||
this.reloadedAutomatically = reloadedAutomatically;
|
||||
}
|
||||
|
||||
public void setProxyUrl(String proxyUrl) {
|
||||
this.proxyUrl = proxyUrl;
|
||||
}
|
||||
|
||||
public void setProxyPath(String proxyPath) {
|
||||
this.proxyPath = proxyPath;
|
||||
}
|
||||
|
||||
public List<String> getSourcePath() {
|
||||
return sourcePath;
|
||||
}
|
||||
|
@ -128,14 +129,16 @@ public class DevServer {
|
|||
servlet.setAutomaticallyReloaded(reloadedAutomatically);
|
||||
servlet.setPort(port);
|
||||
servlet.setDebugPort(debugPort);
|
||||
servlet.setProxyUrl(proxyUrl);
|
||||
servlet.setProxyPath(proxyPath);
|
||||
for (DevServerListener listener : listeners) {
|
||||
servlet.addListener(listener);
|
||||
}
|
||||
context.addServlet(new ServletHolder(servlet), "/*");
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
servletHolder.setAsyncSupported(true);
|
||||
context.addServlet(servletHolder, "/*");
|
||||
|
||||
try {
|
||||
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
|
||||
wscontainer.addEndpoint(new DevServerEndpointConfig(servlet));
|
||||
server.start();
|
||||
server.join();
|
||||
} catch (Exception e) {
|
||||
|
@ -152,52 +155,4 @@ public class DevServer {
|
|||
server = null;
|
||||
servlet = null;
|
||||
}
|
||||
|
||||
private class DevServerEndpointConfig implements ServerEndpointConfig {
|
||||
private Map<String, Object> userProperties = new HashMap<>();
|
||||
|
||||
public DevServerEndpointConfig(CodeServlet servlet) {
|
||||
userProperties.put("teavm.servlet", servlet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Decoder>> getDecoders() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Encoder>> getEncoders() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getUserProperties() {
|
||||
return userProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configurator getConfigurator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEndpointClass() {
|
||||
return CodeWsEndpoint.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Extension> getExtensions() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return pathToFile + fileName + ".ws";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSubprotocols() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2018 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.devserver;
|
||||
|
||||
interface HeaderConsumer {
|
||||
void header(String key, String value);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2018 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.devserver;
|
||||
|
||||
public interface ProgressHandler {
|
||||
void complete(boolean success);
|
||||
|
||||
void progress(double value);
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2018 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.devserver;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
|
||||
@WebSocket
|
||||
public class ProxyWsClient {
|
||||
private Session session;
|
||||
private ProxyWsClient target;
|
||||
private boolean closed;
|
||||
private List<Consumer<ProxyWsClient>> pendingMessages = new ArrayList<>();
|
||||
|
||||
public void setTarget(ProxyWsClient target) {
|
||||
if (this.target != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.target = target;
|
||||
flush();
|
||||
target.flush();
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void connect(Session session) {
|
||||
this.session = session;
|
||||
flush();
|
||||
if (target != null) {
|
||||
target.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void close(int code, String reason) {
|
||||
closed = true;
|
||||
if (!target.closed) {
|
||||
target.closed = true;
|
||||
session.close(code, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(byte[] buf, int offset, int length) {
|
||||
send(t -> t.session.getRemote().sendBytesByFuture(ByteBuffer.wrap(buf, offset, length)));
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(String text) {
|
||||
send(t -> t.session.getRemote().sendStringByFuture(text));
|
||||
}
|
||||
|
||||
private void send(Consumer<ProxyWsClient> message) {
|
||||
if (target == null || target.session == null || !target.session.isOpen()) {
|
||||
if (pendingMessages != null) {
|
||||
pendingMessages.add(message);
|
||||
}
|
||||
} else {
|
||||
message.accept(target);
|
||||
}
|
||||
}
|
||||
|
||||
private void flush() {
|
||||
if (pendingMessages == null || target == null || target.session == null || !target.session.isOpen()) {
|
||||
return;
|
||||
}
|
||||
for (Consumer<ProxyWsClient> message : pendingMessages) {
|
||||
message.accept(target);
|
||||
}
|
||||
pendingMessages = null;
|
||||
}
|
||||
}
|
|
@ -27,4 +27,6 @@ public class DevServerConfiguration {
|
|||
public String pathToFile;
|
||||
public String fileName;
|
||||
public int debugPort;
|
||||
public String proxyUrl;
|
||||
public String proxyPath;
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ public class DevServerRunner extends UnicastRemoteObject implements DevServerMan
|
|||
|
||||
public static void main(String[] args) throws Exception {
|
||||
DevServer server = new DevServer();
|
||||
server.setLog(new ConsoleTeaVMToolLog(true));
|
||||
server.setLog(new ConsoleTeaVMToolLog(false));
|
||||
server.setMainClass(args[0]);
|
||||
List<String> classPath = new ArrayList<>();
|
||||
for (int i = 1; i < args.length; ++i) {
|
||||
|
@ -137,6 +137,12 @@ public class DevServerRunner extends UnicastRemoteObject implements DevServerMan
|
|||
case "-P":
|
||||
server.setDebugPort(Integer.parseInt(args[++i]));
|
||||
break;
|
||||
case "-proxy-url":
|
||||
server.setProxyUrl(args[++i]);
|
||||
break;
|
||||
case "-proxy-path":
|
||||
server.setProxyPath(args[++i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
server.setClassPath(classPath.toArray(new String[0]));
|
||||
|
@ -196,6 +202,15 @@ public class DevServerRunner extends UnicastRemoteObject implements DevServerMan
|
|||
arguments.add(Integer.toString(options.debugPort));
|
||||
}
|
||||
|
||||
if (options.proxyUrl != null && !options.proxyUrl.isEmpty()) {
|
||||
arguments.add("-proxy-url");
|
||||
arguments.add(options.proxyUrl);
|
||||
}
|
||||
if (options.proxyPath != null) {
|
||||
arguments.add("-proxy-path");
|
||||
arguments.add(options.proxyPath);
|
||||
}
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(arguments.toArray(new String[0]));
|
||||
Process process = builder.start();
|
||||
BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream(),
|
||||
|
|
|
@ -46,6 +46,8 @@ public class TeaVMDevServerConfiguration extends ModuleBasedConfiguration<RunCon
|
|||
private boolean indicator = true;
|
||||
private boolean automaticallyReloaded;
|
||||
private int maxHeap = 1024;
|
||||
private String proxyUrl = "";
|
||||
private String proxyPath = "";
|
||||
|
||||
public TeaVMDevServerConfiguration(
|
||||
@NotNull RunConfigurationModule configurationModule,
|
||||
|
@ -172,4 +174,24 @@ public class TeaVMDevServerConfiguration extends ModuleBasedConfiguration<RunCon
|
|||
public void setMaxHeap(int maxHeap) {
|
||||
this.maxHeap = maxHeap;
|
||||
}
|
||||
|
||||
@Property
|
||||
@Tag
|
||||
public String getProxyUrl() {
|
||||
return proxyUrl;
|
||||
}
|
||||
|
||||
public void setProxyUrl(String proxyUrl) {
|
||||
this.proxyUrl = proxyUrl;
|
||||
}
|
||||
|
||||
@Property
|
||||
@Tag
|
||||
public String getProxyPath() {
|
||||
return proxyPath;
|
||||
}
|
||||
|
||||
public void setProxyPath(String proxyPath) {
|
||||
this.proxyPath = proxyPath;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,8 @@ public class TeaVMDevServerRunState implements RunProfileState {
|
|||
config.autoReload = configuration.isAutomaticallyReloaded();
|
||||
config.mainClass = configuration.getMainClass();
|
||||
config.maxHeap = configuration.getMaxHeap();
|
||||
config.proxyUrl = configuration.getProxyUrl();
|
||||
config.proxyPath = configuration.getProxyPath();
|
||||
|
||||
if (executor.getId().equals(DefaultDebugExecutor.EXECUTOR_ID)) {
|
||||
config.debugPort = choosePort();
|
||||
|
|
|
@ -50,6 +50,8 @@ public class TeaVMDevServerSettingsPanel extends JPanel {
|
|||
private JCheckBox indicatorField;
|
||||
private JCheckBox autoReloadField;
|
||||
private JFormattedTextField maxHeapField;
|
||||
private JTextField proxyUrlField;
|
||||
private JTextField proxyPathField;
|
||||
|
||||
public TeaVMDevServerSettingsPanel(Project project) {
|
||||
moduleField = new ModuleDescriptionsComboBox();
|
||||
|
@ -77,6 +79,9 @@ public class TeaVMDevServerSettingsPanel extends JPanel {
|
|||
autoReloadField = new JCheckBox("Reload page automatically:");
|
||||
maxHeapField = new JFormattedTextField(new DecimalFormat("#0"));
|
||||
|
||||
proxyUrlField = new JTextField();
|
||||
proxyPathField = new JTextField();
|
||||
|
||||
initLayout();
|
||||
}
|
||||
|
||||
|
@ -117,6 +122,12 @@ public class TeaVMDevServerSettingsPanel extends JPanel {
|
|||
|
||||
add(new JLabel("Server heap limit:"), labelConstraints);
|
||||
add(maxHeapField, constraints);
|
||||
|
||||
add(new JLabel("Proxy URL:"), labelConstraints);
|
||||
add(proxyUrlField, constraints);
|
||||
|
||||
add(new JLabel("Proxy from path:"), labelConstraints);
|
||||
add(proxyPathField, constraints);
|
||||
}
|
||||
|
||||
public void load(TeaVMDevServerConfiguration configuration) {
|
||||
|
@ -129,21 +140,25 @@ public class TeaVMDevServerSettingsPanel extends JPanel {
|
|||
autoReloadField.setSelected(configuration.isAutomaticallyReloaded());
|
||||
maxHeapField.setText(Integer.toString(configuration.getMaxHeap()));
|
||||
portField.setText(Integer.toString(configuration.getPort()));
|
||||
proxyUrlField.setText(configuration.getProxyUrl());
|
||||
proxyPathField.setText(configuration.getProxyPath());
|
||||
}
|
||||
|
||||
public void save(TeaVMDevServerConfiguration configuration) {
|
||||
configuration.setMainClass(mainClassField.getText());
|
||||
configuration.setMainClass(mainClassField.getText().trim());
|
||||
moduleSelector.applyTo(configuration);
|
||||
configuration.setJdkPath(jrePathEditor.getJrePathOrName());
|
||||
configuration.setFileName(fileNameField.getText());
|
||||
configuration.setPathToFile(pathToFileField.getText());
|
||||
configuration.setFileName(fileNameField.getText().trim());
|
||||
configuration.setPathToFile(pathToFileField.getText().trim());
|
||||
configuration.setIndicator(indicatorField.isSelected());
|
||||
configuration.setAutomaticallyReloaded(autoReloadField.isSelected());
|
||||
if (!maxHeapField.getText().isEmpty()) {
|
||||
if (!maxHeapField.getText().trim().isEmpty()) {
|
||||
configuration.setMaxHeap(Integer.parseInt(maxHeapField.getText()));
|
||||
}
|
||||
if (!portField.getText().isEmpty()) {
|
||||
if (!portField.getText().trim().isEmpty()) {
|
||||
configuration.setPort(Integer.parseInt(portField.getText()));
|
||||
}
|
||||
configuration.setProxyUrl(proxyUrlField.getText().trim());
|
||||
configuration.setProxyPath(proxyPathField.getText().trim());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user