diff options
Diffstat (limited to 'language-web')
-rw-r--r-- | language-web/build.gradle | 24 | ||||
-rw-r--r-- | language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java | 134 |
2 files changed, 104 insertions, 54 deletions
diff --git a/language-web/build.gradle b/language-web/build.gradle index a2acb3b0..6910fea0 100644 --- a/language-web/build.gradle +++ b/language-web/build.gradle | |||
@@ -7,15 +7,13 @@ dependencies { | |||
7 | compile "org.eclipse.xtext:org.eclipse.xtext.web.servlet:${xtextVersion}" | 7 | compile "org.eclipse.xtext:org.eclipse.xtext.web.servlet:${xtextVersion}" |
8 | compile "org.eclipse.xtend:org.eclipse.xtend.lib:${xtextVersion}" | 8 | compile "org.eclipse.xtend:org.eclipse.xtend.lib:${xtextVersion}" |
9 | compile "org.eclipse.jetty:jetty-server:${jettyVersion}" | 9 | compile "org.eclipse.jetty:jetty-server:${jettyVersion}" |
10 | compile "org.eclipse.jetty:jetty-annotations:${jettyVersion}" | 10 | compile "org.eclipse.jetty:jetty-servlet:${jettyVersion}" |
11 | compile "org.slf4j:slf4j-simple:${slf4JVersion}" | 11 | compile "org.slf4j:slf4j-simple:${slf4JVersion}" |
12 | } | 12 | } |
13 | 13 | ||
14 | def webpackOutputDir = "${buildDir}/webpack" | 14 | def webpackOutputDir = "${buildDir}/webpack" |
15 | def productionResources = "${webpackOutputDir}/production" | 15 | def productionResources = "${webpackOutputDir}/production" |
16 | def mainClass = 'org.eclipse.viatra.solver.language.web.ServerLauncher' | 16 | def mainClass = 'org.eclipse.viatra.solver.language.web.ServerLauncher' |
17 | def devMode = System.getenv('NODE_ENV') != 'production' | ||
18 | def currentNodeEnv = devMode ? 'development' : 'production' | ||
19 | 17 | ||
20 | apply plugin: 'com.moowork.node' | 18 | apply plugin: 'com.moowork.node' |
21 | 19 | ||
@@ -71,18 +69,21 @@ shadowJar { | |||
71 | } | 69 | } |
72 | 70 | ||
73 | task jettyRun(type: JavaExec) { | 71 | task jettyRun(type: JavaExec) { |
74 | if (devMode) { | 72 | shouldRunAfter webpackProduction |
75 | dependsOn webpackDevelopment | ||
76 | } else { | ||
77 | dependsOn webpackProduction | ||
78 | } | ||
79 | dependsOn sourceSets.main.runtimeClasspath | 73 | dependsOn sourceSets.main.runtimeClasspath |
80 | classpath = sourceSets.main.runtimeClasspath.filter{it.exists()} | 74 | classpath = sourceSets.main.runtimeClasspath.filter{it.exists()} |
81 | main = mainClass | 75 | main = mainClass |
82 | standardInput = System.in | 76 | standardInput = System.in |
83 | environment BASE_RESOURCE: "${webpackOutputDir}/${currentNodeEnv}" | 77 | environment BASE_RESOURCE: productionResources |
78 | group = 'run' | ||
79 | description = 'Start a Jetty web server serving the Xtex API and assets (without rebuilding assets).' | ||
80 | } | ||
81 | |||
82 | task jettyRunAssets { | ||
83 | dependsOn webpackProduction | ||
84 | dependsOn jettyRun | ||
84 | group = 'run' | 85 | group = 'run' |
85 | description = 'Start a Jetty web server serving the Xtex API and assets.' | 86 | description = 'Rebuild assets and start a Jetty web server serving the Xtex API and assets.' |
86 | } | 87 | } |
87 | 88 | ||
88 | task webpackServe(type: NpmTask) { | 89 | task webpackServe(type: NpmTask) { |
@@ -98,6 +99,9 @@ task webpackServe(type: NpmTask) { | |||
98 | task eslint(type: NpmTask) { | 99 | task eslint(type: NpmTask) { |
99 | dependsOn npmInstall | 100 | dependsOn npmInstall |
100 | args = ['run', 'eslint'] | 101 | args = ['run', 'eslint'] |
102 | inputs.dir 'src/main/js' | ||
103 | inputs.file 'tsconfig.json' | ||
104 | inputs.file '.eslintrc.js' | ||
101 | group = 'verification' | 105 | group = 'verification' |
102 | description = 'Checks for TypeScript errors.' | 106 | description = 'Checks for TypeScript errors.' |
103 | } | 107 | } |
diff --git a/language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java b/language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java index 63088511..d92c7735 100644 --- a/language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java +++ b/language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java | |||
@@ -8,26 +8,62 @@ import java.io.IOException; | |||
8 | import java.net.InetSocketAddress; | 8 | import java.net.InetSocketAddress; |
9 | import java.net.URI; | 9 | import java.net.URI; |
10 | import java.net.URISyntaxException; | 10 | import java.net.URISyntaxException; |
11 | import java.util.Set; | ||
12 | |||
13 | import javax.servlet.SessionTrackingMode; | ||
11 | 14 | ||
12 | import org.eclipse.jetty.server.Server; | 15 | import org.eclipse.jetty.server.Server; |
16 | import org.eclipse.jetty.server.session.SessionHandler; | ||
17 | import org.eclipse.jetty.servlet.DefaultServlet; | ||
18 | import org.eclipse.jetty.servlet.ServletContextHandler; | ||
19 | import org.eclipse.jetty.servlet.ServletHolder; | ||
13 | import org.eclipse.jetty.util.log.Slf4jLog; | 20 | import org.eclipse.jetty.util.log.Slf4jLog; |
14 | import org.eclipse.jetty.util.resource.Resource; | 21 | import org.eclipse.jetty.util.resource.Resource; |
15 | import org.eclipse.jetty.webapp.WebAppContext; | ||
16 | 22 | ||
17 | public class ServerLauncher { | 23 | public class ServerLauncher { |
24 | public static final String DEFAULT_LISTEN_ADDRESS = "localhost"; | ||
25 | |||
26 | public static final int DEFAULT_LISTEN_PORT = 1312; | ||
27 | |||
28 | // Use this cookie name for load balancing. | ||
29 | public static final String SESSION_COOKIE_NAME = "JSESSIONID"; | ||
30 | |||
18 | private static final Slf4jLog LOG = new Slf4jLog(ServerLauncher.class.getName()); | 31 | private static final Slf4jLog LOG = new Slf4jLog(ServerLauncher.class.getName()); |
19 | 32 | ||
20 | private final Server server; | 33 | private final Server server; |
21 | 34 | ||
22 | public ServerLauncher(InetSocketAddress bindAddress, Resource baseResource) { | 35 | public ServerLauncher(InetSocketAddress bindAddress, Resource baseResource) { |
23 | server = new Server(bindAddress); | 36 | server = new Server(bindAddress); |
24 | var ctx = new WebAppContext(); | 37 | var handler = new ServletContextHandler(); |
25 | ctx.setBaseResource(baseResource); | 38 | addSessionHandler(handler); |
26 | ctx.setWelcomeFiles(new String[] { "index.html" }); | 39 | addProblemServlet(handler); |
27 | ctx.setContextPath("/"); | 40 | if (baseResource != null) { |
28 | ctx.addServlet(ProblemServlet.class, "/xtext-service/*"); | 41 | handler.setBaseResource(baseResource); |
29 | ctx.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false"); | 42 | handler.setWelcomeFiles(new String[] { "index.html" }); |
30 | server.setHandler(ctx); | 43 | addDefaultServlet(handler); |
44 | } | ||
45 | server.setHandler(handler); | ||
46 | } | ||
47 | |||
48 | private void addSessionHandler(ServletContextHandler handler) { | ||
49 | var sessionHandler = new SessionHandler(); | ||
50 | sessionHandler.setSessionTrackingModes(Set.of(SessionTrackingMode.COOKIE)); | ||
51 | sessionHandler.setSessionCookie(SESSION_COOKIE_NAME); | ||
52 | handler.setSessionHandler(sessionHandler); | ||
53 | } | ||
54 | |||
55 | private void addProblemServlet(ServletContextHandler handler) { | ||
56 | handler.addServlet(ProblemServlet.class, "/xtext-service/*"); | ||
57 | } | ||
58 | |||
59 | private void addDefaultServlet(ServletContextHandler handler) { | ||
60 | var defaultServletHolder = new ServletHolder(DefaultServlet.class); | ||
61 | var isWindows = System.getProperty("os.name").toLowerCase().contains("win"); | ||
62 | // Avoid file locking on Windows: https://stackoverflow.com/a/4985717 | ||
63 | // See also the related Jetty ticket: | ||
64 | // https://github.com/eclipse/jetty.project/issues/2925 | ||
65 | defaultServletHolder.setInitParameter("useFileMappedBuffer", isWindows ? "false" : "true"); | ||
66 | handler.addServlet(defaultServletHolder, "/"); | ||
31 | } | 67 | } |
32 | 68 | ||
33 | public void start() throws Exception { | 69 | public void start() throws Exception { |
@@ -38,55 +74,65 @@ public class ServerLauncher { | |||
38 | if (key != -1) { | 74 | if (key != -1) { |
39 | server.stop(); | 75 | server.stop(); |
40 | } else { | 76 | } else { |
41 | LOG.warn( | 77 | LOG.warn("Console input is not available. " |
42 | "Console input is not available. In order to stop the server, you need to cancel process manually."); | 78 | + "In order to stop the server, you need to cancel process manually."); |
43 | } | 79 | } |
44 | } | 80 | } |
45 | 81 | ||
46 | private static InetSocketAddress getBindAddress(String listenAddress, int port) { | 82 | public static void main(String[] args) { |
47 | if (listenAddress == null) { | 83 | try { |
48 | return new InetSocketAddress(port); | 84 | var bindAddress = getBindAddress(); |
49 | } | 85 | var baseResource = getBaseResource(); |
50 | return new InetSocketAddress(listenAddress, port); | 86 | var serverLauncher = new ServerLauncher(bindAddress, baseResource); |
51 | } | 87 | serverLauncher.start(); |
52 | 88 | } catch (Exception exception) { | |
53 | private static Resource getBaseResource(String baseResourceOverride) throws IOException, URISyntaxException { | 89 | LOG.warn(exception); |
54 | if (baseResourceOverride != null) { | 90 | System.exit(1); |
55 | return Resource.newResource(baseResourceOverride); | ||
56 | } | ||
57 | var indexUrlInJar = ServerLauncher.class.getResource("/webapp/index.html"); | ||
58 | if (indexUrlInJar == null) { | ||
59 | var workingPath = new String[] { System.getProperty("user.dir"), "build", "webpack", "development", }; | ||
60 | return Resource.newResource(new File(String.join(File.separator, workingPath))); | ||
61 | } | 91 | } |
62 | var webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString().replaceFirst("/index.html$", "/")); | ||
63 | return Resource.newResource(webRootUri); | ||
64 | } | 92 | } |
65 | 93 | ||
66 | public static void main(String[] args) { | 94 | private static String getListenAddress() { |
67 | var listenAddress = System.getenv("LISTEN_ADDRESS"); | 95 | var listenAddress = System.getenv("LISTEN_ADDRESS"); |
68 | if (listenAddress == null) { | 96 | if (listenAddress == null) { |
69 | listenAddress = "localhost"; | 97 | return DEFAULT_LISTEN_ADDRESS; |
70 | } | 98 | } |
71 | var port = 1312; | 99 | return listenAddress; |
100 | } | ||
101 | |||
102 | private static int getListenPort() { | ||
72 | var portStr = System.getenv("LISTEN_PORT"); | 103 | var portStr = System.getenv("LISTEN_PORT"); |
73 | if (portStr != null) { | 104 | if (portStr != null) { |
74 | try { | 105 | return Integer.parseInt(portStr); |
75 | port = Integer.parseInt(portStr); | ||
76 | } catch (NumberFormatException e) { | ||
77 | LOG.warn(e); | ||
78 | System.exit(1); | ||
79 | } | ||
80 | } | 106 | } |
107 | return DEFAULT_LISTEN_PORT; | ||
108 | } | ||
109 | |||
110 | private static InetSocketAddress getBindAddress() { | ||
111 | var listenAddress = getListenAddress(); | ||
112 | var listenPort = getListenPort(); | ||
113 | return new InetSocketAddress(listenAddress, listenPort); | ||
114 | } | ||
115 | |||
116 | private static Resource getBaseResource() throws IOException, URISyntaxException { | ||
81 | var baseResourceOverride = System.getenv("BASE_RESOURCE"); | 117 | var baseResourceOverride = System.getenv("BASE_RESOURCE"); |
82 | try { | 118 | if (baseResourceOverride != null) { |
83 | var bindAddress = getBindAddress(listenAddress, port); | 119 | // If a user override is provided, use it. |
84 | var baseResource = getBaseResource(baseResourceOverride); | 120 | return Resource.newResource(baseResourceOverride); |
85 | var serverLauncher = new ServerLauncher(bindAddress, baseResource); | 121 | } |
86 | serverLauncher.start(); | 122 | var indexUrlInJar = ServerLauncher.class.getResource("/webapp/index.html"); |
87 | } catch (Exception exception) { | 123 | if (indexUrlInJar != null) { |
88 | LOG.warn(exception); | 124 | // If the app is packaged in the jar, serve it. |
89 | System.exit(1); | 125 | var webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString().replaceFirst("/index.html$", "/")); |
126 | return Resource.newResource(webRootUri); | ||
127 | } | ||
128 | // Look for unpacked production artifacts (convenience for running from IDE). | ||
129 | var unpackedResourcePathComponents = new String[] { System.getProperty("user.dir"), "build", "webpack", | ||
130 | "production" }; | ||
131 | var unpackedResourceDir = new File(String.join(File.separator, unpackedResourcePathComponents)); | ||
132 | if (unpackedResourceDir.isDirectory()) { | ||
133 | return Resource.newResource(unpackedResourceDir); | ||
90 | } | 134 | } |
135 | // Fall back to just serving a 404. | ||
136 | return null; | ||
91 | } | 137 | } |
92 | } | 138 | } |