From 4971c864a603e0c01f7ad84a23697905d096283b Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 10 Nov 2022 14:35:24 +0100 Subject: feat(web): backend URL configuration To point the frontend to a backend server, update the config.json file in the website root. The config.json is generated automatically in debug mode and when running from a standalone jar. --- .../refinery/language/web/CacheControlFilter.java | 5 +-- .../refinery/language/web/ServerLauncher.java | 29 ++++++++++++++-- .../language/web/config/BackendConfig.java | 20 +++++++++++ .../language/web/config/BackendConfigServlet.java | 39 ++++++++++++++++++++++ .../web/xtext/server/ResponseHandlerException.java | 3 ++ .../web/xtext/server/TransactionExecutor.java | 39 ++++++++-------------- .../language/web/xtext/servlet/XtextWebSocket.java | 22 +++++------- 7 files changed, 114 insertions(+), 43 deletions(-) create mode 100644 subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java create mode 100644 subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java (limited to 'subprojects/language-web/src') diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java index fbce62c1..fd2af1b2 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java @@ -13,8 +13,9 @@ import java.util.regex.Pattern; public class CacheControlFilter implements Filter { private static final Pattern CACHE_URI_PATTERN = Pattern.compile(".*\\.(css|gif|js|map|png|svg|woff2?)"); - private static final Set CACHE_URI_DENYLIST = Set.of("apple-touch-icon.png", "favicon.png", "favicon.svg", - "favicon-96x96.png", "icon-any.svg", "icon-192x192.png", "icon-512x512.png", "mask-icon.svg", "sw.js"); + private static final Set CACHE_URI_DENYLIST = Set.of("apple-touch-icon.png", "config.json", "favicon.png", + "favicon.svg", "favicon-96x96.png", "icon-any.svg", "icon-192x192.png", "icon-512x512.png", "mask-icon.svg", + "sw.js"); private static final Duration EXPIRY = Duration.ofDays(365); diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java index ffa61321..58c8ea4e 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java @@ -14,6 +14,7 @@ import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import tools.refinery.language.web.config.BackendConfigServlet; import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; import java.io.File; @@ -41,11 +42,13 @@ public class ServerLauncher { private final Server server; - public ServerLauncher(InetSocketAddress bindAddress, Resource baseResource, String[] allowedOrigins) { + public ServerLauncher(InetSocketAddress bindAddress, Resource baseResource, String[] allowedOrigins, + String webSocketUrl) { server = new Server(bindAddress); var handler = new ServletContextHandler(); addSessionHandler(handler); addProblemServlet(handler, allowedOrigins); + addBackendConfigServlet(handler, webSocketUrl); if (baseResource != null) { handler.setBaseResource(baseResource); handler.setWelcomeFiles(new String[]{"index.html"}); @@ -76,6 +79,12 @@ public class ServerLauncher { JettyWebSocketServletContainerInitializer.configure(handler, null); } + private void addBackendConfigServlet(ServletContextHandler handler, String webSocketUrl) { + var backendConfigServletHolder = new ServletHolder(BackendConfigServlet.class); + backendConfigServletHolder.setInitParameter(BackendConfigServlet.WEBSOCKET_URL_INIT_PARAM, webSocketUrl); + handler.addServlet(backendConfigServletHolder, "/config.json"); + } + private void addDefaultServlet(ServletContextHandler handler) { var defaultServletHolder = new ServletHolder(DefaultServlet.class); var isWindows = System.getProperty("os.name").toLowerCase().contains("win"); @@ -97,7 +106,8 @@ public class ServerLauncher { var bindAddress = getBindAddress(); var baseResource = getBaseResource(); var allowedOrigins = getAllowedOrigins(); - var serverLauncher = new ServerLauncher(bindAddress, baseResource, allowedOrigins); + var webSocketUrl = getWebSocketUrl(); + var serverLauncher = new ServerLauncher(bindAddress, baseResource, allowedOrigins, webSocketUrl); serverLauncher.start(); } catch (Exception exception) { LOG.error("Fatal server error", exception); @@ -190,4 +200,19 @@ public class ServerLauncher { } return new String[]{urlWithPort}; } + + private static String getWebSocketUrl() { + String host; + int port; + var publicHost = getPublicHost(); + if (publicHost == null) { + host = getListenAddress(); + port = getListenPort(); + } else { + host = publicHost; + port = getPublicPort(); + } + var scheme = port == HTTPS_DEFAULT_PORT ? "wss" : "ws"; + return String.format("%s://%s:%d/xtext-service", scheme, host, port); + } } diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java new file mode 100644 index 00000000..2e864998 --- /dev/null +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java @@ -0,0 +1,20 @@ +package tools.refinery.language.web.config; + +import com.google.gson.annotations.SerializedName; + +public class BackendConfig { + @SerializedName("webSocketURL") + private String webSocketUrl; + + public BackendConfig(String webSocketUrl) { + this.webSocketUrl = webSocketUrl; + } + + public String getWebSocketUrl() { + return webSocketUrl; + } + + public void setWebSocketUrl(String webSocketUrl) { + this.webSocketUrl = webSocketUrl; + } +} diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java new file mode 100644 index 00000000..f314a9fa --- /dev/null +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java @@ -0,0 +1,39 @@ +package tools.refinery.language.web.config; + +import com.google.gson.Gson; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpStatus; + +import java.io.IOException; + +public class BackendConfigServlet extends HttpServlet { + public static final String WEBSOCKET_URL_INIT_PARAM = "tools.refinery.language.web.config.BackendConfigServlet" + + ".webSocketUrl"; + + private String serializedConfig; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + var webSocketUrl = config.getInitParameter(WEBSOCKET_URL_INIT_PARAM); + if (webSocketUrl == null) { + throw new IllegalArgumentException("Init parameter " + WEBSOCKET_URL_INIT_PARAM + " is mandatory"); + } + var backendConfig = new BackendConfig(webSocketUrl); + var gson = new Gson(); + serializedConfig = gson.toJson(backendConfig); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setStatus(HttpStatus.OK_200); + resp.setContentType("application/json"); + var writer = resp.getWriter(); + writer.write(serializedConfig); + writer.flush(); + } +} diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java index 34fcb546..b686d33a 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java @@ -1,7 +1,10 @@ package tools.refinery.language.web.xtext.server; +import java.io.Serial; + public class ResponseHandlerException extends Exception { + @Serial private static final long serialVersionUID = 3589866922420268164L; public ResponseHandlerException(String message, Throwable cause) { diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java index 0b417b06..7bb11d2e 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java @@ -1,35 +1,25 @@ package tools.refinery.language.web.xtext.server; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import com.google.common.base.Strings; +import com.google.inject.Injector; import org.eclipse.emf.common.util.URI; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.util.IDisposable; -import org.eclipse.xtext.web.server.IServiceContext; -import org.eclipse.xtext.web.server.IServiceResult; -import org.eclipse.xtext.web.server.ISession; -import org.eclipse.xtext.web.server.InvalidRequestException; +import org.eclipse.xtext.web.server.*; import org.eclipse.xtext.web.server.InvalidRequestException.UnknownLanguageException; -import org.eclipse.xtext.web.server.XtextServiceDispatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import com.google.common.base.Strings; -import com.google.inject.Injector; - -import tools.refinery.language.web.xtext.server.message.XtextWebErrorKind; -import tools.refinery.language.web.xtext.server.message.XtextWebErrorResponse; -import tools.refinery.language.web.xtext.server.message.XtextWebOkResponse; -import tools.refinery.language.web.xtext.server.message.XtextWebPushMessage; -import tools.refinery.language.web.xtext.server.message.XtextWebRequest; +import tools.refinery.language.web.xtext.server.message.*; import tools.refinery.language.web.xtext.server.push.PrecomputationListener; import tools.refinery.language.web.xtext.server.push.PushWebDocument; import tools.refinery.language.web.xtext.servlet.SimpleServiceContext; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class TransactionExecutor implements IDisposable, PrecomputationListener { private static final Logger LOG = LoggerFactory.getLogger(TransactionExecutor.class); @@ -41,11 +31,11 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener private ResponseHandler responseHandler; - private Object callPendingLock = new Object(); + private final Object callPendingLock = new Object(); private boolean callPending; - private List pendingPushMessages = new ArrayList<>(); + private final List pendingPushMessages = new ArrayList<>(); public TransactionExecutor(ISession session, IResourceServiceProvider.Registry resourceServiceProviderRegistry) { this.session = session; @@ -132,10 +122,9 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener /** * Get the injector to satisfy the request in the {@code serviceContext}. - * * Based on {@link org.eclipse.xtext.web.servlet.XtextServlet#getInjector}. - * - * @param serviceContext the Xtext service context of the request + * + * @param context the Xtext service context of the request * @return the injector for the Xtext language in the request * @throws UnknownLanguageException if the Xtext language cannot be determined */ diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java index fd41f213..82391d8b 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java @@ -1,31 +1,25 @@ package tools.refinery.language.web.xtext.servlet; -import java.io.IOException; -import java.io.Reader; - +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.google.gson.JsonParseException; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WriteCallback; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.annotations.*; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.web.server.ISession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; -import com.google.gson.JsonIOException; -import com.google.gson.JsonParseException; - import tools.refinery.language.web.xtext.server.ResponseHandler; import tools.refinery.language.web.xtext.server.ResponseHandlerException; import tools.refinery.language.web.xtext.server.TransactionExecutor; import tools.refinery.language.web.xtext.server.message.XtextWebRequest; import tools.refinery.language.web.xtext.server.message.XtextWebResponse; +import java.io.IOException; +import java.io.Reader; + @WebSocket public class XtextWebSocket implements WriteCallback, ResponseHandler { private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class); @@ -118,7 +112,7 @@ public class XtextWebSocket implements WriteCallback, ResponseHandler { webSocketSession.getRemote().sendPartialString(responseString, true, this); } catch (IOException e) { throw new ResponseHandlerException( - "Cannot initiaite async write to websocket " + webSocketSession.getRemoteAddress(), e); + "Cannot initiate async write to websocket " + webSocketSession.getRemoteAddress(), e); } } -- cgit v1.2.3-54-g00ecf