From ce6feb91a1cc08da12ce1d91f6af44828b25c20b Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Fri, 6 Sep 2024 17:12:12 -0500 Subject: [PATCH 1/5] Rename all existing jetty projects to jetty-11 --- docker/server-jetty/build.gradle | 2 +- gradle/libs.versions.toml | 19 ++++++------ py/embedded-server/java-runtime/build.gradle | 2 +- .../python/server/EmbeddedServer.java | 8 ++--- server/{jetty => jetty-11}/build.gradle | 16 +++++----- server/{jetty => jetty-11}/gradle.properties | 0 .../server/jetty11}/CacheFilter.java | 2 +- .../jetty11}/CommunityComponentFactory.java | 2 +- .../jetty11}/ControlledCacheResource.java | 2 +- .../deephaven/server/jetty11}/CopyHelper.java | 2 +- .../jetty11}/DropIfModifiedSinceHeader.java | 2 +- .../deephaven/server/jetty11}/GrpcFilter.java | 2 +- .../deephaven/server/jetty11}/HomeFilter.java | 2 +- .../jetty11}/JettyBackedGrpcServer.java | 2 +- .../server/jetty11}/JettyCertInterceptor.java | 2 +- .../JettyClientChannelFactoryModule.java | 2 +- .../server/jetty11}/JettyConfig.java | 2 +- .../server/jetty11}/JettyServerComponent.java | 2 +- .../server/jetty11}/JettyServerModule.java | 2 +- .../deephaven/server/jetty11}/JsPlugins.java | 2 +- .../jetty11}/JsPluginsZipFilesystem.java | 4 +-- .../io/deephaven/server/jetty11}/Json.java | 2 +- .../server/jetty11}/NoCacheFilter.java | 2 +- .../server/jetty11}/WebsocketFactory.java | 2 +- .../jetty11}/JettyFlightRoundTripTest.java | 6 ++-- .../jetty11}/js/Example123Registration.java | 2 +- .../server/jetty11}/js/Sentinel.java | 2 +- .../js/JsPluginsManifestRegistration.java | 0 .../js/JsPluginsNpmPackageRegistration.java | 0 .../@deephaven_test/example1/dist/index.js | 0 .../@deephaven_test/example1/dist/index2.js | 0 .../@deephaven_test/example1/package.json | 0 .../@deephaven_test/example2/dist/index.js | 0 .../@deephaven_test/example2/dist/index2.js | 0 .../@deephaven_test/example2/package.json | 0 .../@deephaven_test/example3/index.js | 0 .../server/jetty11}/js/examples/manifest.json | 0 server/{jetty-app => jetty-app-11}/README.md | 31 +++++++++++-------- .../{jetty-app => jetty-app-11}/build.gradle | 6 ++-- .../gradle.properties | 0 .../deephaven/server/jetty11}/JettyMain.java | 2 +- .../src/main/resources/logback-debug.xml | 0 .../src/main/resources/logback-minimal.xml | 0 .../src/main/resources/logback.xml | 0 .../README.md | 2 +- .../build.gradle | 4 +-- .../gradle.properties | 0 .../server/custom/CustomApplication1.java | 0 .../server/custom/CustomApplication2.java | 0 .../server/custom/CustomAuthorization.java | 0 .../CustomClientChannelFactoryModule.java | 0 .../server/custom/CustomComponentFactory.java | 9 ++---- .../deephaven/server/custom/CustomMain.java | 0 .../src/main/resources/logback.xml | 0 settings.gradle | 9 ++++++ 55 files changed, 85 insertions(+), 73 deletions(-) rename server/{jetty => jetty-11}/build.gradle (79%) rename server/{jetty => jetty-11}/gradle.properties (100%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/CacheFilter.java (97%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/CommunityComponentFactory.java (98%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/ControlledCacheResource.java (99%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/CopyHelper.java (98%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/DropIfModifiedSinceHeader.java (98%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/GrpcFilter.java (98%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/HomeFilter.java (97%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/JettyBackedGrpcServer.java (99%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/JettyCertInterceptor.java (95%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/JettyClientChannelFactoryModule.java (95%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/JettyConfig.java (99%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/JettyServerComponent.java (93%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/JettyServerModule.java (98%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/JsPlugins.java (96%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/JsPluginsZipFilesystem.java (97%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/Json.java (90%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/NoCacheFilter.java (97%) rename server/{jetty/src/main/java/io/deephaven/server/jetty => jetty-11/src/main/java/io/deephaven/server/jetty11}/WebsocketFactory.java (98%) rename server/{jetty/src/test/java/io/deephaven/server/jetty => jetty-11/src/test/java/io/deephaven/server/jetty11}/JettyFlightRoundTripTest.java (98%) rename server/{jetty/src/test/java/io/deephaven/server/jetty => jetty-11/src/test/java/io/deephaven/server/jetty11}/js/Example123Registration.java (98%) rename server/{jetty/src/test/java/io/deephaven/server/jetty => jetty-11/src/test/java/io/deephaven/server/jetty11}/js/Sentinel.java (75%) rename server/{jetty => jetty-11}/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java (100%) rename server/{jetty => jetty-11}/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java (100%) rename server/{jetty/src/test/resources/io/deephaven/server/jetty => jetty-11/src/test/resources/io/deephaven/server/jetty11}/js/examples/@deephaven_test/example1/dist/index.js (100%) rename server/{jetty/src/test/resources/io/deephaven/server/jetty => jetty-11/src/test/resources/io/deephaven/server/jetty11}/js/examples/@deephaven_test/example1/dist/index2.js (100%) rename server/{jetty/src/test/resources/io/deephaven/server/jetty => jetty-11/src/test/resources/io/deephaven/server/jetty11}/js/examples/@deephaven_test/example1/package.json (100%) rename server/{jetty/src/test/resources/io/deephaven/server/jetty => jetty-11/src/test/resources/io/deephaven/server/jetty11}/js/examples/@deephaven_test/example2/dist/index.js (100%) rename server/{jetty/src/test/resources/io/deephaven/server/jetty => jetty-11/src/test/resources/io/deephaven/server/jetty11}/js/examples/@deephaven_test/example2/dist/index2.js (100%) rename server/{jetty/src/test/resources/io/deephaven/server/jetty => jetty-11/src/test/resources/io/deephaven/server/jetty11}/js/examples/@deephaven_test/example2/package.json (100%) rename server/{jetty/src/test/resources/io/deephaven/server/jetty => jetty-11/src/test/resources/io/deephaven/server/jetty11}/js/examples/@deephaven_test/example3/index.js (100%) rename server/{jetty/src/test/resources/io/deephaven/server/jetty => jetty-11/src/test/resources/io/deephaven/server/jetty11}/js/examples/manifest.json (100%) rename server/{jetty-app => jetty-app-11}/README.md (85%) rename server/{jetty-app => jetty-app-11}/build.gradle (96%) rename server/{jetty-app-custom => jetty-app-11}/gradle.properties (100%) rename server/{jetty-app/src/main/java/io/deephaven/server/jetty => jetty-app-11/src/main/java/io/deephaven/server/jetty11}/JettyMain.java (95%) rename server/{jetty-app => jetty-app-11}/src/main/resources/logback-debug.xml (100%) rename server/{jetty-app => jetty-app-11}/src/main/resources/logback-minimal.xml (100%) rename server/{jetty-app => jetty-app-11}/src/main/resources/logback.xml (100%) rename server/{jetty-app-custom => jetty-app-custom-11}/README.md (86%) rename server/{jetty-app-custom => jetty-app-custom-11}/build.gradle (96%) rename server/{jetty-app => jetty-app-custom-11}/gradle.properties (100%) rename server/{jetty-app-custom => jetty-app-custom-11}/src/main/java/io/deephaven/server/custom/CustomApplication1.java (100%) rename server/{jetty-app-custom => jetty-app-custom-11}/src/main/java/io/deephaven/server/custom/CustomApplication2.java (100%) rename server/{jetty-app-custom => jetty-app-custom-11}/src/main/java/io/deephaven/server/custom/CustomAuthorization.java (100%) rename server/{jetty-app-custom => jetty-app-custom-11}/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java (100%) rename server/{jetty-app-custom => jetty-app-custom-11}/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java (92%) rename server/{jetty-app-custom => jetty-app-custom-11}/src/main/java/io/deephaven/server/custom/CustomMain.java (100%) rename server/{jetty-app-custom => jetty-app-custom-11}/src/main/resources/logback.xml (100%) diff --git a/docker/server-jetty/build.gradle b/docker/server-jetty/build.gradle index e498b50987c..ce7a6b8a209 100644 --- a/docker/server-jetty/build.gradle +++ b/docker/server-jetty/build.gradle @@ -28,7 +28,7 @@ configurations { } dependencies { - serverApplicationDist project(path: ':server-jetty-app', configuration: 'applicationDist') + serverApplicationDist project(path: ':server-jetty-app-11', configuration: 'applicationDist')//revert me pythonWheel project(':py-server') } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b4fb36cbd85..0a9d1980ea0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,7 +50,7 @@ javax-inject = "1" javax-validation = "1.0.0.GA" jdom2 = "2.0.6.1" jetbrains = "24.1.0" -jetty = "11.0.20" +jetty11 = "11.0.20" jpy = "0.18.0" jsinterop = "2.0.2" # google is annoying, and have different versions released for the same groupId @@ -224,14 +224,15 @@ jdom2 = { module = "org.jdom:jdom2", version.ref = "jdom2" } jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains" } -jetty-alpn-java-server = { module = "org.eclipse.jetty:jetty-alpn-java-server" } -jetty-alpn-server = { module = "org.eclipse.jetty:jetty-alpn-server" } -jetty-bom = { module = "org.eclipse.jetty:jetty-bom", version.ref = "jetty" } -jetty-http2-server = { module = "org.eclipse.jetty.http2:http2-server" } -jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet" } -jetty-servlets = { module = "org.eclipse.jetty:jetty-servlets" } -jetty-webapp = { module = "org.eclipse.jetty:jetty-webapp" } -jetty-websocket-jakarta-server = { module = "org.eclipse.jetty.websocket:websocket-jakarta-server" } + +jetty11-alpn-java-server = { module = "org.eclipse.jetty:jetty-alpn-java-server" } +jetty11-alpn-server = { module = "org.eclipse.jetty:jetty-alpn-server" } +jetty11-bom = { module = "org.eclipse.jetty:jetty-bom", version.ref = "jetty11" } +jetty11-http2-server = { module = "org.eclipse.jetty.http2:http2-server" } +jetty11-servlet = { module = "org.eclipse.jetty:jetty-servlet" } +jetty11-servlets = { module = "org.eclipse.jetty:jetty-servlets" } +jetty11-webapp = { module = "org.eclipse.jetty:jetty-webapp" } +jetty11-websocket-jakarta-server = { module = "org.eclipse.jetty.websocket:websocket-jakarta-server" } jpy = { module = "org.jpyconsortium:jpy", version.ref = "jpy" } diff --git a/py/embedded-server/java-runtime/build.gradle b/py/embedded-server/java-runtime/build.gradle index 1fadd12b881..84b95a99276 100644 --- a/py/embedded-server/java-runtime/build.gradle +++ b/py/embedded-server/java-runtime/build.gradle @@ -9,7 +9,7 @@ configurations { } dependencies { - implementation project(':server-jetty') + implementation project(':server-jetty-11')//revert me implementation libs.dagger annotationProcessor libs.dagger.compiler diff --git a/py/embedded-server/java-runtime/src/main/java/io/deephaven/python/server/EmbeddedServer.java b/py/embedded-server/java-runtime/src/main/java/io/deephaven/python/server/EmbeddedServer.java index 3bc427f8904..5e5b7e779fb 100644 --- a/py/embedded-server/java-runtime/src/main/java/io/deephaven/python/server/EmbeddedServer.java +++ b/py/embedded-server/java-runtime/src/main/java/io/deephaven/python/server/EmbeddedServer.java @@ -22,10 +22,10 @@ import io.deephaven.server.console.python.PythonConsoleSessionModule; import io.deephaven.server.console.python.PythonGlobalScopeModule; import io.deephaven.server.healthcheck.HealthCheckModule; -import io.deephaven.server.jetty.JettyConfig; -import io.deephaven.server.jetty.JettyConfig.Builder; -import io.deephaven.server.jetty.JettyServerComponent; -import io.deephaven.server.jetty.JettyServerModule; +import io.deephaven.server.jetty11.JettyConfig; +import io.deephaven.server.jetty11.JettyConfig.Builder; +import io.deephaven.server.jetty11.JettyServerComponent; +import io.deephaven.server.jetty11.JettyServerModule; import io.deephaven.server.plugin.python.PythonPluginsRegistration; import io.deephaven.server.runner.DeephavenApiConfigModule; import io.deephaven.server.runner.DeephavenApiServer; diff --git a/server/jetty/build.gradle b/server/jetty-11/build.gradle similarity index 79% rename from server/jetty/build.gradle rename to server/jetty-11/build.gradle index a02fb606602..423a372d79a 100644 --- a/server/jetty/build.gradle +++ b/server/jetty-11/build.gradle @@ -19,23 +19,23 @@ dependencies { testAnnotationProcessor libs.dagger.compiler implementation platform(libs.grpc.bom) - implementation platform(libs.jetty.bom) + implementation platform(libs.jetty11.bom) api libs.jakarata.servlet.api - implementation libs.jetty.servlet - implementation libs.jetty.servlets - implementation libs.jetty.webapp - implementation libs.jetty.http2.server - implementation libs.jetty.alpn.server + implementation libs.jetty11.servlet + implementation libs.jetty11.servlets + implementation libs.jetty11.webapp + implementation libs.jetty11.http2.server + implementation libs.jetty11.alpn.server // TODO(deephaven-core#2506): Support for alternative ALPN implementations - runtimeOnly libs.jetty.alpn.java.server + runtimeOnly libs.jetty11.alpn.java.server // implementation 'io.grpc:grpc-servlet-jakarta' api(project(':grpc-java:grpc-servlet-jakarta')) { because 'downstream dagger compile' } implementation project(':grpc-java:grpc-servlet-websocket-jakarta') - implementation libs.jetty.websocket.jakarta.server + implementation libs.jetty11.websocket.jakarta.server compileOnly project(':util-immutables') annotationProcessor libs.immutables.value diff --git a/server/jetty/gradle.properties b/server/jetty-11/gradle.properties similarity index 100% rename from server/jetty/gradle.properties rename to server/jetty-11/gradle.properties diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/CacheFilter.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/CacheFilter.java similarity index 97% rename from server/jetty/src/main/java/io/deephaven/server/jetty/CacheFilter.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/CacheFilter.java index deb095b5150..8f93c0b7f4a 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/CacheFilter.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/CacheFilter.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/CommunityComponentFactory.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/CommunityComponentFactory.java similarity index 98% rename from server/jetty/src/main/java/io/deephaven/server/jetty/CommunityComponentFactory.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/CommunityComponentFactory.java index 014eaae59eb..7e46b035068 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/CommunityComponentFactory.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/CommunityComponentFactory.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import dagger.Component; import dagger.Module; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/ControlledCacheResource.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/ControlledCacheResource.java similarity index 99% rename from server/jetty/src/main/java/io/deephaven/server/jetty/ControlledCacheResource.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/ControlledCacheResource.java index 2a7dbab20d4..fe02768a10b 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/ControlledCacheResource.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/ControlledCacheResource.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import org.eclipse.jetty.util.resource.Resource; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/CopyHelper.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/CopyHelper.java similarity index 98% rename from server/jetty/src/main/java/io/deephaven/server/jetty/CopyHelper.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/CopyHelper.java index c407f5aca60..15e0ce4fa53 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/CopyHelper.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/CopyHelper.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/DropIfModifiedSinceHeader.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/DropIfModifiedSinceHeader.java similarity index 98% rename from server/jetty/src/main/java/io/deephaven/server/jetty/DropIfModifiedSinceHeader.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/DropIfModifiedSinceHeader.java index 433a86a5327..6e095bb77a3 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/DropIfModifiedSinceHeader.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/DropIfModifiedSinceHeader.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/GrpcFilter.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/GrpcFilter.java similarity index 98% rename from server/jetty/src/main/java/io/deephaven/server/jetty/GrpcFilter.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/GrpcFilter.java index 04e499b1175..ccf996201db 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/GrpcFilter.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/GrpcFilter.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import io.deephaven.configuration.Configuration; import io.grpc.servlet.jakarta.ServletAdapter; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/HomeFilter.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/HomeFilter.java similarity index 97% rename from server/jetty/src/main/java/io/deephaven/server/jetty/HomeFilter.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/HomeFilter.java index 8bd64a49f32..48e5faab6f1 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/HomeFilter.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/HomeFilter.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyBackedGrpcServer.java similarity index 99% rename from server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyBackedGrpcServer.java index 6122b287bcc..a9b8ad5b2d0 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyBackedGrpcServer.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import io.deephaven.server.browserstreaming.BrowserStreamInterceptor; import io.deephaven.server.runner.GrpcServer; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyCertInterceptor.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyCertInterceptor.java similarity index 95% rename from server/jetty/src/main/java/io/deephaven/server/jetty/JettyCertInterceptor.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyCertInterceptor.java index eed4cc1e35c..c5e5857c6d3 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyCertInterceptor.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyCertInterceptor.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import io.deephaven.grpc.AbstractMtlsClientCertificateInterceptor; import io.grpc.ServerCall; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyClientChannelFactoryModule.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyClientChannelFactoryModule.java similarity index 95% rename from server/jetty/src/main/java/io/deephaven/server/jetty/JettyClientChannelFactoryModule.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyClientChannelFactoryModule.java index 3646d125631..5aebc69169a 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyClientChannelFactoryModule.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyClientChannelFactoryModule.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import dagger.Module; import dagger.Provides; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyConfig.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyConfig.java similarity index 99% rename from server/jetty/src/main/java/io/deephaven/server/jetty/JettyConfig.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyConfig.java index 109cd2371d3..527575ddef2 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyConfig.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyConfig.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import io.deephaven.annotations.BuildableStyle; import io.deephaven.configuration.Configuration; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerComponent.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyServerComponent.java similarity index 93% rename from server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerComponent.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyServerComponent.java index eeb756a8f1b..232f7be94ba 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerComponent.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyServerComponent.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import dagger.BindsInstance; import io.deephaven.server.runner.DeephavenApiServerComponent; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerModule.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyServerModule.java similarity index 98% rename from server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerModule.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyServerModule.java index ce105435c90..bf65a3d700d 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerModule.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JettyServerModule.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import dagger.Binds; import dagger.Module; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JsPlugins.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JsPlugins.java similarity index 96% rename from server/jetty/src/main/java/io/deephaven/server/jetty/JsPlugins.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/JsPlugins.java index 40017a27737..67b0d7d5bbe 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JsPlugins.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JsPlugins.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import io.deephaven.plugin.js.JsPlugin; import io.deephaven.plugin.js.JsPluginRegistration; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JsPluginsZipFilesystem.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JsPluginsZipFilesystem.java similarity index 97% rename from server/jetty/src/main/java/io/deephaven/server/jetty/JsPluginsZipFilesystem.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/JsPluginsZipFilesystem.java index 9e8dedded60..9d94a3aaf8d 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JsPluginsZipFilesystem.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/JsPluginsZipFilesystem.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import io.deephaven.configuration.CacheDir; import io.deephaven.plugin.js.JsPlugin; @@ -21,7 +21,7 @@ import java.util.Map; import java.util.Objects; -import static io.deephaven.server.jetty.Json.OBJECT_MAPPER; +import static io.deephaven.server.jetty11.Json.OBJECT_MAPPER; import static io.deephaven.server.plugin.js.JsPluginManifest.MANIFEST_JSON; class JsPluginsZipFilesystem { diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/Json.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/Json.java similarity index 90% rename from server/jetty/src/main/java/io/deephaven/server/jetty/Json.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/Json.java index 8ff9b2e079e..7e145218502 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/Json.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/Json.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/NoCacheFilter.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/NoCacheFilter.java similarity index 97% rename from server/jetty/src/main/java/io/deephaven/server/jetty/NoCacheFilter.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/NoCacheFilter.java index 21d0118b69b..7afcb32c687 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/NoCacheFilter.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/NoCacheFilter.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/WebsocketFactory.java b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/WebsocketFactory.java similarity index 98% rename from server/jetty/src/main/java/io/deephaven/server/jetty/WebsocketFactory.java rename to server/jetty-11/src/main/java/io/deephaven/server/jetty11/WebsocketFactory.java index a034239e3e2..2d898e4b704 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/WebsocketFactory.java +++ b/server/jetty-11/src/main/java/io/deephaven/server/jetty11/WebsocketFactory.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import io.grpc.servlet.web.websocket.GrpcWebsocket; import io.grpc.servlet.web.websocket.MultiplexedWebSocketServerStream; diff --git a/server/jetty/src/test/java/io/deephaven/server/jetty/JettyFlightRoundTripTest.java b/server/jetty-11/src/test/java/io/deephaven/server/jetty11/JettyFlightRoundTripTest.java similarity index 98% rename from server/jetty/src/test/java/io/deephaven/server/jetty/JettyFlightRoundTripTest.java rename to server/jetty-11/src/test/java/io/deephaven/server/jetty11/JettyFlightRoundTripTest.java index 84a1c6b9253..73a3f829aa1 100644 --- a/server/jetty/src/test/java/io/deephaven/server/jetty/JettyFlightRoundTripTest.java +++ b/server/jetty-11/src/test/java/io/deephaven/server/jetty11/JettyFlightRoundTripTest.java @@ -1,13 +1,13 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import dagger.Component; import dagger.Module; import dagger.Provides; -import io.deephaven.server.jetty.js.Example123Registration; -import io.deephaven.server.jetty.js.Sentinel; +import io.deephaven.server.jetty11.js.Example123Registration; +import io.deephaven.server.jetty11.js.Sentinel; import io.deephaven.server.plugin.js.JsPluginsManifestRegistration; import io.deephaven.server.plugin.js.JsPluginsNpmPackageRegistration; import io.deephaven.server.runner.ExecutionContextUnitTestModule; diff --git a/server/jetty/src/test/java/io/deephaven/server/jetty/js/Example123Registration.java b/server/jetty-11/src/test/java/io/deephaven/server/jetty11/js/Example123Registration.java similarity index 98% rename from server/jetty/src/test/java/io/deephaven/server/jetty/js/Example123Registration.java rename to server/jetty-11/src/test/java/io/deephaven/server/jetty11/js/Example123Registration.java index b88294bd7dd..7d14c1352ae 100644 --- a/server/jetty/src/test/java/io/deephaven/server/jetty/js/Example123Registration.java +++ b/server/jetty-11/src/test/java/io/deephaven/server/jetty11/js/Example123Registration.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty.js; +package io.deephaven.server.jetty11.js; import io.deephaven.plugin.Registration; import io.deephaven.plugin.js.JsPlugin; diff --git a/server/jetty/src/test/java/io/deephaven/server/jetty/js/Sentinel.java b/server/jetty-11/src/test/java/io/deephaven/server/jetty11/js/Sentinel.java similarity index 75% rename from server/jetty/src/test/java/io/deephaven/server/jetty/js/Sentinel.java rename to server/jetty-11/src/test/java/io/deephaven/server/jetty11/js/Sentinel.java index 6f1e7056ba8..f09be4d019d 100644 --- a/server/jetty/src/test/java/io/deephaven/server/jetty/js/Sentinel.java +++ b/server/jetty-11/src/test/java/io/deephaven/server/jetty11/js/Sentinel.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty.js; +package io.deephaven.server.jetty11.js; public class Sentinel { // just for the class diff --git a/server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java b/server/jetty-11/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java similarity index 100% rename from server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java rename to server/jetty-11/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java diff --git a/server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java b/server/jetty-11/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java similarity index 100% rename from server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java rename to server/jetty-11/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index.js b/server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example1/dist/index.js similarity index 100% rename from server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index.js rename to server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example1/dist/index.js diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index2.js b/server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example1/dist/index2.js similarity index 100% rename from server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index2.js rename to server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example1/dist/index2.js diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/package.json b/server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example1/package.json similarity index 100% rename from server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/package.json rename to server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example1/package.json diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index.js b/server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example2/dist/index.js similarity index 100% rename from server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index.js rename to server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example2/dist/index.js diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index2.js b/server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example2/dist/index2.js similarity index 100% rename from server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index2.js rename to server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example2/dist/index2.js diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/package.json b/server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example2/package.json similarity index 100% rename from server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/package.json rename to server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example2/package.json diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example3/index.js b/server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example3/index.js similarity index 100% rename from server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example3/index.js rename to server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/@deephaven_test/example3/index.js diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/manifest.json b/server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/manifest.json similarity index 100% rename from server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/manifest.json rename to server/jetty-11/src/test/resources/io/deephaven/server/jetty11/js/examples/manifest.json diff --git a/server/jetty-app/README.md b/server/jetty-app-11/README.md similarity index 85% rename from server/jetty-app/README.md rename to server/jetty-app-11/README.md index 5f4538c942c..1428e59b95f 100644 --- a/server/jetty-app/README.md +++ b/server/jetty-app-11/README.md @@ -1,3 +1,8 @@ +# Deprecated +Note, this is deprecated, as Jetty 11 has reached end of community support, and is projected to reach end of life Jan 2025. +Instead, please use our [Jetty 12.x based server](../jetty-app). + + # server-jetty-app This README is oriented towards getting a server up for local development in a development environment. From a development environment, users can modify source code towards contributing to the project, creating custom capabilities, and more. @@ -11,10 +16,10 @@ This README deals with general development for either the Python or Groovy serve ## Local development -`./gradlew server-jetty-app:run` will incorporate local Java changes on each start. If you are not frequently changing Java code, see the next section. +`./gradlew server-jetty-app-11:run` will incorporate local Java changes on each start. If you are not frequently changing Java code, see the next section. ```shell -./gradlew server-jetty-app:run +./gradlew server-jetty-app-11:run ``` ## Development with infrequent changes @@ -22,8 +27,8 @@ This README deals with general development for either the Python or Groovy serve To create a more production-like environment, you can create and invoke the start script instead of running via gradle. This is faster if you need to often restart the server without making any changes to Java code (such as Python server development). ```shell -./gradlew server-jetty-app:installDist # Run after any Java changes -./server/jetty-app/build/install/server-jetty/bin/start +./gradlew server-jetty-app-11:installDist # Run after any Java changes +./server/jetty-app-11/build/install/server-jetty-11/bin/start ``` The specifics of the start script can be found in [unixStartScript.txt](../../buildSrc/src/main/resources/unixStartScript.txt), @@ -47,10 +52,10 @@ The following is a list of some "quick" gradle properties that can be set to cha These are typically used ad-hoc as part of a gradlew command: ```shell -./gradlew server-jetty-app:run # Python session (default) -./gradlew server-jetty-app:run -Pdebug # Attach a Java debugger to the Python session on port 5005 -./gradlew server-jetty-app:run -Pgroovy # Groovy session -./gradlew server-jetty-app:run -Pgroovy -Pdebug # Attach a Java debugger to the Groovy session on port 5005 +./gradlew server-jetty-app-11:run # Python session (default) +./gradlew server-jetty-app-11:run -Pdebug # Attach a Java debugger to the Python session on port 5005 +./gradlew server-jetty-app-11:run -Pgroovy # Groovy session +./gradlew server-jetty-app-11:run -Pgroovy -Pdebug # Attach a Java debugger to the Groovy session on port 5005 ``` The specifics for these quick configurations can be found in [build.gradle](build.gradle). @@ -61,7 +66,7 @@ There are a few different ways to configure JVM arguments for development purpos The `START_OPTS` environment variable is a user-specific configuration most useful for setting JVM heap size and other related JVM options: ```shell -START_OPTS="-Xmx12g" ./gradlew server-jetty-app:run # Starts Deephaven with 12gb of heap memory +START_OPTS="-Xmx12g" ./gradlew server-jetty-app-11:run # Starts Deephaven with 12gb of heap memory ``` The `JAVA_OPTS` environment variable that allows the user to override the Deephaven-recommended JVM arguments @@ -69,7 +74,7 @@ The `JAVA_OPTS` environment variable that allows the user to override the Deepha For example, to use the ZGC generational garbage collector: ```shell -JAVA_OPTS="-XX:+UseZGC -XX:+ZGenerational" START_OPTS="-Xmx12g" ./gradlew server-jetty-app:run # Starts Deephaven with ZGC generational collector with 12gb of heap memory +JAVA_OPTS="-XX:+UseZGC -XX:+ZGenerational" START_OPTS="-Xmx12g" ./gradlew server-jetty-app-11:run # Starts Deephaven with ZGC generational collector with 12gb of heap memory ``` These environmental variables mimic the behavior of the environmental variables from the [native application script](https://deephaven.io/core/docs/how-to-guides/configuration/native-application/#native-application-script). @@ -77,7 +82,7 @@ These environmental variables mimic the behavior of the environmental variables These JVM arguments can also be configured through the gradle properties `deephaven.startOpts` and `deephaven.javaOpts` respectively: ```shell -./gradlew server-jetty-app:run -Pdeephaven.startOpts="-Xmx12g" # Starts Deephaven with 12gb of heap memory +./gradlew server-jetty-app-11:run -Pdeephaven.startOpts="-Xmx12g" # Starts Deephaven with 12gb of heap memory ``` More commonly though, this provides a mechanism for developers to persist their own Deephaven JVM arguments as gradle properties in `~./gradle/gradle.properties`: @@ -132,13 +137,13 @@ expiration duration of 5 minutes, a scheduler pool size of 4, and a max inbound To bring up a SSL-enabled server on port 8443 with a development key and certificate, you can run: ```shell -./gradlew server-jetty-app:run -Pgroovy -PdevCerts +./gradlew server-jetty-app-11:run -Pgroovy -PdevCerts ``` SSL configuration can be applied manually with the properties "ssl.identity.type", "ssl.identity.certChainPath", "ssl.identity.privateKeyPath", "ssl.trust.type", and "ssl.trust.path". Furthermore, outbound Deephaven-to-Deephaven connections can be explicitly configured separately if desired, with the same properties prefixed with "outbound.". -See the javadocs on `io.deephaven.server.jetty.JettyConfig` and `io.deephaven.server.runner.Main.parseSSLConfig` for +See the javadocs on `io.deephaven.server.jetty11.JettyConfig` and `io.deephaven.server.runner.Main.parseSSLConfig` for more information. ### SSL examples diff --git a/server/jetty-app/build.gradle b/server/jetty-app-11/build.gradle similarity index 96% rename from server/jetty-app/build.gradle rename to server/jetty-app-11/build.gradle index acce172c114..02c79393af6 100644 --- a/server/jetty-app/build.gradle +++ b/server/jetty-app-11/build.gradle @@ -9,7 +9,7 @@ configurations { } dependencies { - implementation project(':server-jetty') + implementation project(':server-jetty-11') runtimeOnly project(':log-to-slf4j') runtimeOnly project(':logback-print-stream-globals') @@ -19,7 +19,7 @@ dependencies { distributions { main { - distributionBaseName = 'server-jetty' + distributionBaseName = 'server-jetty-11' } } @@ -130,7 +130,7 @@ tasks.withType(CreateStartScripts).configureEach { application { applicationName = 'start' - mainClass = 'io.deephaven.server.jetty.JettyMain' + mainClass = 'io.deephaven.server.jetty11.JettyMain' } artifacts { diff --git a/server/jetty-app-custom/gradle.properties b/server/jetty-app-11/gradle.properties similarity index 100% rename from server/jetty-app-custom/gradle.properties rename to server/jetty-app-11/gradle.properties diff --git a/server/jetty-app/src/main/java/io/deephaven/server/jetty/JettyMain.java b/server/jetty-app-11/src/main/java/io/deephaven/server/jetty11/JettyMain.java similarity index 95% rename from server/jetty-app/src/main/java/io/deephaven/server/jetty/JettyMain.java rename to server/jetty-app-11/src/main/java/io/deephaven/server/jetty11/JettyMain.java index 9c78b757d59..402a3f58d8b 100644 --- a/server/jetty-app/src/main/java/io/deephaven/server/jetty/JettyMain.java +++ b/server/jetty-app-11/src/main/java/io/deephaven/server/jetty11/JettyMain.java @@ -1,7 +1,7 @@ // // Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // -package io.deephaven.server.jetty; +package io.deephaven.server.jetty11; import io.deephaven.configuration.Configuration; import io.deephaven.server.runner.MainHelper; diff --git a/server/jetty-app/src/main/resources/logback-debug.xml b/server/jetty-app-11/src/main/resources/logback-debug.xml similarity index 100% rename from server/jetty-app/src/main/resources/logback-debug.xml rename to server/jetty-app-11/src/main/resources/logback-debug.xml diff --git a/server/jetty-app/src/main/resources/logback-minimal.xml b/server/jetty-app-11/src/main/resources/logback-minimal.xml similarity index 100% rename from server/jetty-app/src/main/resources/logback-minimal.xml rename to server/jetty-app-11/src/main/resources/logback-minimal.xml diff --git a/server/jetty-app/src/main/resources/logback.xml b/server/jetty-app-11/src/main/resources/logback.xml similarity index 100% rename from server/jetty-app/src/main/resources/logback.xml rename to server/jetty-app-11/src/main/resources/logback.xml diff --git a/server/jetty-app-custom/README.md b/server/jetty-app-custom-11/README.md similarity index 86% rename from server/jetty-app-custom/README.md rename to server/jetty-app-custom-11/README.md index 15a6592be88..3c8ce1efccc 100644 --- a/server/jetty-app-custom/README.md +++ b/server/jetty-app-custom-11/README.md @@ -9,5 +9,5 @@ starting example. ## Quickstart ```shell -./gradlew server-jetty-app-custom:run -Pgroovy +./gradlew server-jetty-app-custom-11:run -Pgroovy ``` diff --git a/server/jetty-app-custom/build.gradle b/server/jetty-app-custom-11/build.gradle similarity index 96% rename from server/jetty-app-custom/build.gradle rename to server/jetty-app-custom-11/build.gradle index b66da129e31..0c095726d31 100644 --- a/server/jetty-app-custom/build.gradle +++ b/server/jetty-app-custom-11/build.gradle @@ -8,7 +8,7 @@ configurations { } dependencies { - implementation project(':server-jetty') + implementation project(':server-jetty-11') implementation libs.dagger annotationProcessor libs.dagger.compiler @@ -20,7 +20,7 @@ dependencies { distributions { main { - distributionBaseName = 'server-jetty-custom' + distributionBaseName = 'server-jetty-custom-11' } } diff --git a/server/jetty-app/gradle.properties b/server/jetty-app-custom-11/gradle.properties similarity index 100% rename from server/jetty-app/gradle.properties rename to server/jetty-app-custom-11/gradle.properties diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication1.java b/server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomApplication1.java similarity index 100% rename from server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication1.java rename to server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomApplication1.java diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication2.java b/server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomApplication2.java similarity index 100% rename from server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication2.java rename to server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomApplication2.java diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomAuthorization.java b/server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomAuthorization.java similarity index 100% rename from server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomAuthorization.java rename to server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomAuthorization.java diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java b/server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java similarity index 100% rename from server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java rename to server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java b/server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java similarity index 92% rename from server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java rename to server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java index 22cf12662b8..31839c90e14 100644 --- a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java +++ b/server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java @@ -10,20 +10,17 @@ import dagger.Provides; import dagger.multibindings.IntoSet; import io.deephaven.appmode.ApplicationState; -import io.deephaven.client.impl.BarrageSessionFactoryConfig; import io.deephaven.configuration.Configuration; import io.deephaven.server.auth.AuthorizationProvider; import io.deephaven.server.custom.CustomComponentFactory.CustomComponent; -import io.deephaven.server.jetty.JettyServerComponent; -import io.deephaven.server.jetty.JettyConfig; -import io.deephaven.server.jetty.JettyServerModule; +import io.deephaven.server.jetty11.JettyServerComponent; +import io.deephaven.server.jetty11.JettyConfig; +import io.deephaven.server.jetty11.JettyServerModule; import io.deephaven.server.runner.CommunityDefaultsModule; import io.deephaven.server.runner.ComponentFactoryBase; -import io.deephaven.server.session.ClientChannelFactoryModule.UserAgent; import javax.inject.Singleton; import java.io.PrintStream; -import java.util.List; /** * An example of a "custom integrator" component factory. This is not meant to be an exhaustive example of Deephaven diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomMain.java b/server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomMain.java similarity index 100% rename from server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomMain.java rename to server/jetty-app-custom-11/src/main/java/io/deephaven/server/custom/CustomMain.java diff --git a/server/jetty-app-custom/src/main/resources/logback.xml b/server/jetty-app-custom-11/src/main/resources/logback.xml similarity index 100% rename from server/jetty-app-custom/src/main/resources/logback.xml rename to server/jetty-app-custom-11/src/main/resources/logback.xml diff --git a/settings.gradle b/settings.gradle index 9db07a453c4..74c5cc7733b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -125,6 +125,15 @@ project(':server-jetty-app').projectDir = file('server/jetty-app') include(':server-jetty-app-custom') project(':server-jetty-app-custom').projectDir = file('server/jetty-app-custom') +include(':server-jetty-11') +project(':server-jetty-11').projectDir = file('server/jetty-11') + +include(':server-jetty-app-11') +project(':server-jetty-app-11').projectDir = file('server/jetty-app-11') + +include(':server-jetty-app-custom-11') +project(':server-jetty-app-custom-11').projectDir = file('server/jetty-app-custom-11') + include(':Base') include(':base-test-utils') From 874497fda822c642b0745160e3f6f577e59a95fb Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Fri, 6 Sep 2024 20:26:12 -0500 Subject: [PATCH 2/5] Introduce jetty 12 implementation --- ...eephaven.java-toolchain-conventions.gradle | 2 +- gradle/libs.versions.toml | 11 + server/jetty-app-custom/README.md | 13 + server/jetty-app-custom/build.gradle | 113 ++++++ server/jetty-app-custom/gradle.properties | 6 + .../server/custom/CustomApplication1.java | 39 ++ .../server/custom/CustomApplication2.java | 37 ++ .../server/custom/CustomAuthorization.java | 25 ++ .../CustomClientChannelFactoryModule.java | 26 ++ .../server/custom/CustomComponentFactory.java | 100 +++++ .../deephaven/server/custom/CustomMain.java | 25 ++ .../src/main/resources/logback.xml | 30 ++ server/jetty-app/README.md | 201 +++++++++ server/jetty-app/build.gradle | 140 +++++++ server/jetty-app/gradle.properties | 6 + .../io/deephaven/server/jetty/JettyMain.java | 27 ++ .../src/main/resources/logback-debug.xml | 32 ++ .../src/main/resources/logback-minimal.xml | 21 + .../jetty-app/src/main/resources/logback.xml | 39 ++ server/jetty/build.gradle | 59 +++ server/jetty/gradle.properties | 6 + .../deephaven/server/jetty/CacheFilter.java | 39 ++ .../jetty/CommunityComponentFactory.java | 80 ++++ .../server/jetty/ControlledCacheResource.java | 108 +++++ .../io/deephaven/server/jetty/CopyHelper.java | 81 ++++ .../jetty/DropIfModifiedSinceHeader.java | 89 ++++ .../io/deephaven/server/jetty/GrpcFilter.java | 63 +++ .../io/deephaven/server/jetty/HomeFilter.java | 33 ++ .../server/jetty/JettyBackedGrpcServer.java | 380 ++++++++++++++++++ .../server/jetty/JettyCertInterceptor.java | 22 + .../JettyClientChannelFactoryModule.java | 26 ++ .../deephaven/server/jetty/JettyConfig.java | 227 +++++++++++ .../server/jetty/JettyServerComponent.java | 17 + .../server/jetty/JettyServerModule.java | 83 ++++ .../io/deephaven/server/jetty/JsPlugins.java | 43 ++ .../server/jetty/JsPluginsZipFilesystem.java | 117 ++++++ .../java/io/deephaven/server/jetty/Json.java | 12 + .../deephaven/server/jetty/NoCacheFilter.java | 35 ++ .../server/jetty/WebsocketFactory.java | 73 ++++ .../jetty/JettyFlightRoundTripTest.java | 196 +++++++++ .../jetty/js/Example123Registration.java | 68 ++++ .../deephaven/server/jetty/js/Sentinel.java | 8 + .../js/JsPluginsManifestRegistration.java | 35 ++ .../js/JsPluginsNpmPackageRegistration.java | 32 ++ .../@deephaven_test/example1/dist/index.js | 1 + .../@deephaven_test/example1/dist/index2.js | 1 + .../@deephaven_test/example1/package.json | 1 + .../@deephaven_test/example2/dist/index.js | 1 + .../@deephaven_test/example2/dist/index2.js | 1 + .../@deephaven_test/example2/package.json | 1 + .../@deephaven_test/example3/index.js | 1 + .../server/jetty/js/examples/manifest.json | 19 + 52 files changed, 2850 insertions(+), 1 deletion(-) create mode 100644 server/jetty-app-custom/README.md create mode 100644 server/jetty-app-custom/build.gradle create mode 100644 server/jetty-app-custom/gradle.properties create mode 100644 server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication1.java create mode 100644 server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication2.java create mode 100644 server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomAuthorization.java create mode 100644 server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java create mode 100644 server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java create mode 100644 server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomMain.java create mode 100644 server/jetty-app-custom/src/main/resources/logback.xml create mode 100644 server/jetty-app/README.md create mode 100644 server/jetty-app/build.gradle create mode 100644 server/jetty-app/gradle.properties create mode 100644 server/jetty-app/src/main/java/io/deephaven/server/jetty/JettyMain.java create mode 100644 server/jetty-app/src/main/resources/logback-debug.xml create mode 100644 server/jetty-app/src/main/resources/logback-minimal.xml create mode 100644 server/jetty-app/src/main/resources/logback.xml create mode 100644 server/jetty/build.gradle create mode 100644 server/jetty/gradle.properties create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/CacheFilter.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/CommunityComponentFactory.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/ControlledCacheResource.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/CopyHelper.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/DropIfModifiedSinceHeader.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/GrpcFilter.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/HomeFilter.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/JettyCertInterceptor.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/JettyClientChannelFactoryModule.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/JettyConfig.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerComponent.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerModule.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/JsPlugins.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/JsPluginsZipFilesystem.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/Json.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/NoCacheFilter.java create mode 100644 server/jetty/src/main/java/io/deephaven/server/jetty/WebsocketFactory.java create mode 100644 server/jetty/src/test/java/io/deephaven/server/jetty/JettyFlightRoundTripTest.java create mode 100644 server/jetty/src/test/java/io/deephaven/server/jetty/js/Example123Registration.java create mode 100644 server/jetty/src/test/java/io/deephaven/server/jetty/js/Sentinel.java create mode 100644 server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java create mode 100644 server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java create mode 100644 server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index.js create mode 100644 server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index2.js create mode 100644 server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/package.json create mode 100644 server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index.js create mode 100644 server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index2.js create mode 100644 server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/package.json create mode 100644 server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example3/index.js create mode 100644 server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/manifest.json diff --git a/buildSrc/src/main/groovy/io.deephaven.java-toolchain-conventions.gradle b/buildSrc/src/main/groovy/io.deephaven.java-toolchain-conventions.gradle index 2a1322225bc..dc32261bfea 100644 --- a/buildSrc/src/main/groovy/io.deephaven.java-toolchain-conventions.gradle +++ b/buildSrc/src/main/groovy/io.deephaven.java-toolchain-conventions.gradle @@ -16,7 +16,7 @@ def testRuntimeVersion = Integer.parseInt((String)project.findProperty('testRunt def testRuntimeVendor = project.hasProperty('testRuntimeVendor') ? JvmVendorSpec.matching((String)project.property('testRuntimeVendor')): null if (languageLevel > compilerVersion) { - throw new IllegalArgumentException("languageLevel must be less than or equal to compileVersion") + throw new IllegalArgumentException("languageLevel must be less than or equal to compilerVersion") } if (languageLevel < 8) { throw new IllegalArgumentException("languageLevel must be greater than or equal to 8") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a9d1980ea0..911796efb0b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,6 +50,7 @@ javax-inject = "1" javax-validation = "1.0.0.GA" jdom2 = "2.0.6.1" jetbrains = "24.1.0" +jetty = "12.0.13" jetty11 = "11.0.20" jpy = "0.18.0" jsinterop = "2.0.2" @@ -225,6 +226,16 @@ jdom2 = { module = "org.jdom:jdom2", version.ref = "jdom2" } jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains" } +jetty-alpn-java-server = { module = "org.eclipse.jetty:jetty-alpn-java-server" } +jetty-alpn-server = { module = "org.eclipse.jetty:jetty-alpn-server" } +jetty-bom = { module = "org.eclipse.jetty:jetty-bom", version.ref = "jetty" } +jetty-http2-server = { module = "org.eclipse.jetty.http2:jetty-http2-server" } +jetty-ee10-bom = { module = "org.eclipse.jetty.ee10:jetty-ee10-bom", version.ref = "jetty" } +jetty-webapp = { module = "org.eclipse.jetty.ee10:jetty-ee10-webapp" } +jetty-servlet = { module = "org.eclipse.jetty.ee10:jetty-ee10-servlet" } +jetty-servlets = { module = "org.eclipse.jetty.ee10:jetty-ee10-servlets" } +jetty-websocket-jakarta-server = { module = "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server" } + jetty11-alpn-java-server = { module = "org.eclipse.jetty:jetty-alpn-java-server" } jetty11-alpn-server = { module = "org.eclipse.jetty:jetty-alpn-server" } jetty11-bom = { module = "org.eclipse.jetty:jetty-bom", version.ref = "jetty11" } diff --git a/server/jetty-app-custom/README.md b/server/jetty-app-custom/README.md new file mode 100644 index 00000000000..15a6592be88 --- /dev/null +++ b/server/jetty-app-custom/README.md @@ -0,0 +1,13 @@ +# Example integration + +This module has been setup as an example for how an integrator can customize the structure and configuration of the +Deephaven server. + +It is not meant to be an exhaustive example of Deephaven configuration points, nor dagger conventions, but rather as a +starting example. + +## Quickstart + +```shell +./gradlew server-jetty-app-custom:run -Pgroovy +``` diff --git a/server/jetty-app-custom/build.gradle b/server/jetty-app-custom/build.gradle new file mode 100644 index 00000000000..b66da129e31 --- /dev/null +++ b/server/jetty-app-custom/build.gradle @@ -0,0 +1,113 @@ +plugins { + id 'application' + id 'io.deephaven.project.register' +} + +configurations { + applicationDist +} + +dependencies { + implementation project(':server-jetty') + implementation libs.dagger + annotationProcessor libs.dagger.compiler + + runtimeOnly project(':log-to-slf4j') + runtimeOnly project(':logback-print-stream-globals') + runtimeOnly project(':logback-logbuffer') + runtimeOnly libs.logback.classic +} + +distributions { + main { + distributionBaseName = 'server-jetty-custom' + } +} + +def extraJvmArgs = [ + '-server', + '-XshowSettings:vm', +] + +if (hasProperty('groovy')) { + extraJvmArgs += ['-Ddeephaven.console.type=groovy'] +} + +if (!hasProperty('excludeHotspotImpl')) { + dependencies { + runtimeOnly project(':hotspot-impl') + } + extraJvmArgs += ['--add-exports', 'java.management/sun.management=ALL-UNNAMED'] +} + +if (!hasProperty('excludeClockImpl')) { + dependencies { + runtimeOnly project(':clock-impl') + } + extraJvmArgs += ['--add-exports', 'java.base/jdk.internal.misc=ALL-UNNAMED'] +} + +if (!hasProperty('excludeSql')) { + dependencies { + runtimeOnly project(':engine-sql') + } +} + +if (!hasProperty('excludeS3')) { + dependencies { + runtimeOnly project(':extensions-s3') + runtimeOnly project(':extensions-iceberg-s3') + } +} + +if (hasProperty('devCerts') || hasProperty('devMTLS')) { + extraJvmArgs += [ + '-Dhttp.port=8443', + '-Dssl.identity.type=privatekey', + '-Dssl.identity.certChainPath=../dev-certs/server.chain.crt', + '-Dssl.identity.privateKeyPath=../dev-certs/server.key', + ] + if (hasProperty('devMTLS')) { + extraJvmArgs += [ + '-Dssl.trust.type=certs', + '-Dssl.trust.path=../dev-certs/ca.crt', + '-Dssl.clientAuthentication=NEEDED', + ] + } +} + +if (hasProperty('debug')) { + extraJvmArgs += ['-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'] +} + +if (hasProperty('debugAutocomplete')) { + extraJvmArgs += ['-Ddeephaven.console.autocomplete.quiet=false'] +} + +if (hasProperty('gcApplication')) { + extraJvmArgs += ['-Dio.deephaven.app.GcApplication.enabled=true'] +} + +if (hasProperty('quiet')) { + extraJvmArgs += ['-Ddeephaven.quiet=true'] +} + +tasks.withType(JavaExec).configureEach { + // This appends to the existing jvm args, so that java-open-nio still takes effect + jvmArgs extraJvmArgs +} + +tasks.withType(CreateStartScripts).configureEach { + defaultJvmOpts += extraJvmArgs +} + +application { + applicationName = 'start' + mainClass = 'io.deephaven.server.custom.CustomMain' +} + +artifacts { + applicationDist project.tasks.findByName('distTar') +} + +apply plugin: 'io.deephaven.java-open-nio' diff --git a/server/jetty-app-custom/gradle.properties b/server/jetty-app-custom/gradle.properties new file mode 100644 index 00000000000..0ec544952f1 --- /dev/null +++ b/server/jetty-app-custom/gradle.properties @@ -0,0 +1,6 @@ +io.deephaven.project.ProjectType=JAVA_APPLICATION +compilerVersion=17 +runtimeVersion=17 +languageLevel=17 +testRuntimeVersion=17 +testLanguageLevel=17 diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication1.java b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication1.java new file mode 100644 index 00000000000..c46b2082733 --- /dev/null +++ b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication1.java @@ -0,0 +1,39 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.custom; + +import io.deephaven.appmode.ApplicationState; +import io.deephaven.appmode.ApplicationState.Listener; +import io.deephaven.engine.liveness.LivenessScope; +import io.deephaven.engine.liveness.LivenessScopeStack; +import io.deephaven.engine.util.TableTools; +import io.deephaven.util.SafeCloseable; + +import java.util.Objects; + +/** + * Simple application that creates a single-celled string table named {@value #FIELD_NAME}. + */ +public final class CustomApplication1 implements ApplicationState.Factory { + public static final String FIELD_NAME = "app1_value"; + + private final String value; + @SuppressWarnings("FieldCanBeLocal") + private LivenessScope scope; + + public CustomApplication1(String value) { + this.value = Objects.requireNonNull(value); + } + + @Override + public ApplicationState create(Listener appStateListener) { + final ApplicationState state = new ApplicationState(appStateListener, CustomApplication1.class.getName(), + CustomApplication1.class.getSimpleName()); + scope = new LivenessScope(); + try (final SafeCloseable ignored = LivenessScopeStack.open(scope, false)) { + state.setField(FIELD_NAME, TableTools.newTable(TableTools.stringCol("value", value))); + } + return state; + } +} diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication2.java b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication2.java new file mode 100644 index 00000000000..d43cd590adb --- /dev/null +++ b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomApplication2.java @@ -0,0 +1,37 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.custom; + +import io.deephaven.appmode.ApplicationState; +import io.deephaven.appmode.ApplicationState.Listener; +import io.deephaven.engine.liveness.LivenessScope; +import io.deephaven.engine.liveness.LivenessScopeStack; +import io.deephaven.engine.util.TableTools; +import io.deephaven.util.SafeCloseable; + +/** + * Simple application that creates a single-celled int table named {@value #FIELD_NAME}. + */ +public final class CustomApplication2 implements ApplicationState.Factory { + public static final String FIELD_NAME = "app2_value"; + + private final int value; + @SuppressWarnings("FieldCanBeLocal") + private LivenessScope scope; + + public CustomApplication2(int value) { + this.value = value; + } + + @Override + public ApplicationState create(Listener appStateListener) { + final ApplicationState state = new ApplicationState(appStateListener, CustomApplication2.class.getName(), + CustomApplication2.class.getSimpleName()); + scope = new LivenessScope(); + try (final SafeCloseable ignored = LivenessScopeStack.open(scope, false)) { + state.setField(FIELD_NAME, TableTools.newTable(TableTools.intCol("value", value))); + } + return state; + } +} diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomAuthorization.java b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomAuthorization.java new file mode 100644 index 00000000000..1e4b628d5dc --- /dev/null +++ b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomAuthorization.java @@ -0,0 +1,25 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.custom; + +import io.deephaven.auth.codegen.impl.InputTableServiceContextualAuthWiring; +import io.deephaven.server.auth.AllowAllAuthorizationProvider; + +import javax.inject.Inject; + +/** + * Simple authorization that "allows all" except "denys all" for the {@link #getInputTableServiceContextualAuthWiring() + * input table service}. + */ +public final class CustomAuthorization extends AllowAllAuthorizationProvider { + + @Inject + public CustomAuthorization() {} + + @Override + public InputTableServiceContextualAuthWiring getInputTableServiceContextualAuthWiring() { + // Disable input table service + return new InputTableServiceContextualAuthWiring.DenyAll(); + } +} diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java new file mode 100644 index 00000000000..b0a01dfd554 --- /dev/null +++ b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomClientChannelFactoryModule.java @@ -0,0 +1,26 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.custom; + +import dagger.Module; +import dagger.Provides; +import io.deephaven.client.impl.BarrageSessionFactoryConfig; +import io.deephaven.server.session.ClientChannelFactoryModule; +import io.deephaven.server.session.ClientChannelFactoryModule.UserAgent; +import io.deephaven.server.session.SslConfigModule; + +import java.util.List; + +@Module(includes = { + ClientChannelFactoryModule.class, + SslConfigModule.class +}) +public interface CustomClientChannelFactoryModule { + + @Provides + @UserAgent + static String providesUserAgent() { + return BarrageSessionFactoryConfig.userAgent(List.of("deephaven-server-jetty-custom")); + } +} diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java new file mode 100644 index 00000000000..f5f8551ad5e --- /dev/null +++ b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomComponentFactory.java @@ -0,0 +1,100 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.custom; + +import dagger.Binds; +import dagger.BindsInstance; +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoSet; +import io.deephaven.appmode.ApplicationState; +import io.deephaven.configuration.Configuration; +import io.deephaven.server.auth.AuthorizationProvider; +import io.deephaven.server.custom.CustomComponentFactory.CustomComponent; +import io.deephaven.server.jetty.JettyConfig; +import io.deephaven.server.jetty.JettyServerComponent; +import io.deephaven.server.jetty.JettyServerModule; +import io.deephaven.server.runner.CommunityDefaultsModule; +import io.deephaven.server.runner.ComponentFactoryBase; + +import javax.inject.Singleton; +import java.io.PrintStream; + +/** + * An example of a "custom integrator" component factory. This is not meant to be an exhaustive example of Deephaven + * configuration points, nor dagger conventions, but rather as a starting example for implementing + * {@link ComponentFactoryBase}. + * + *

+ * {@link CustomApplication1} is configured with the configuration property {@value APP1_VALUE_PROP} if present, + * otherwise {@value APP1_VALUE_DEFAULT}. + * + *

+ * {@link CustomApplication2} is configured with the configuration property {@value APP2_VALUE_PROP} if present, + * otherwise {@value APP2_VALUE_DEFAULT}. + */ +public final class CustomComponentFactory extends ComponentFactoryBase { + + public static final String APP1_VALUE_PROP = "app1.value"; + public static final String APP2_VALUE_PROP = "app2.value"; + public static final String APP1_VALUE_DEFAULT = "hello, world"; + public static final int APP2_VALUE_DEFAULT = 42; + + @Override + public CustomComponent build(Configuration configuration, PrintStream out, PrintStream err) { + final JettyConfig jettyConfig = JettyConfig.buildFromConfig(configuration).build(); + return DaggerCustomComponentFactory_CustomComponent.builder() + .withOut(out) + .withErr(err) + .withJettyConfig(jettyConfig) + // Bind CustomApplication1 directly + .withCustomApplication1( + new CustomApplication1(configuration.getStringWithDefault(APP1_VALUE_PROP, APP1_VALUE_DEFAULT))) + .build(); + } + + // Dagger will generate DaggerCustomComponentFactory_CustomComponent at compile time based on the annotations + // attached to CustomComponent and CustomComponent.Builder. + @Singleton + @Component(modules = CustomModule.class) + public interface CustomComponent extends JettyServerComponent { + + @Component.Builder + interface Builder extends JettyServerComponent.Builder { + // Use @BindsInstance annotation for supplying CustomApplication1 directly + @BindsInstance + Builder withCustomApplication1(CustomApplication1 app1); + } + } + + @Module(includes = { + JettyServerModule.class, + CustomClientChannelFactoryModule.class, + CommunityDefaultsModule.class, + }) + public interface CustomModule { + + // Use @Provides annotation for CustomApplication2 + @Provides + static CustomApplication2 providesApp2() { + final int value = Configuration.getInstance().getIntegerWithDefault(APP2_VALUE_PROP, APP2_VALUE_DEFAULT); + return new CustomApplication2(value); + } + + // Register CustomApplication1 as an application + @Binds + @IntoSet + ApplicationState.Factory providesApplication1(CustomApplication1 app1); + + // Register CustomApplication2 as an application + @Binds + @IntoSet + ApplicationState.Factory providesApplication2(CustomApplication2 app2); + + // Register CustomAuthorization as the authorization provider + @Binds + AuthorizationProvider bindsAuthorizationProvider(CustomAuthorization customAuthorization); + } +} diff --git a/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomMain.java b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomMain.java new file mode 100644 index 00000000000..35684e7af1d --- /dev/null +++ b/server/jetty-app-custom/src/main/java/io/deephaven/server/custom/CustomMain.java @@ -0,0 +1,25 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.custom; + +import io.deephaven.configuration.Configuration; +import io.deephaven.server.runner.MainHelper; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +/** + * An example of a "custom integrator" main using a {@link CustomComponentFactory}. + */ +public final class CustomMain { + public static void main(String[] args) + throws IOException, InterruptedException, ClassNotFoundException, TimeoutException { + final Configuration configuration = MainHelper.init(args, CustomMain.class); + new CustomComponentFactory() + .build(configuration) + .getServer() + .run() + .join(); + } +} diff --git a/server/jetty-app-custom/src/main/resources/logback.xml b/server/jetty-app-custom/src/main/resources/logback.xml new file mode 100644 index 00000000000..f69901b2c78 --- /dev/null +++ b/server/jetty-app-custom/src/main/resources/logback.xml @@ -0,0 +1,30 @@ + + + + + + + + %d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z', UTC} | %green(%-20.20thread) | %highlight(%5level) | %yellow(%-25.25logger{25}) | %m%n + + + + + + + + %-20.20thread | %-25.25logger{25} | %m + + + + + + + + + + + + diff --git a/server/jetty-app/README.md b/server/jetty-app/README.md new file mode 100644 index 00000000000..5f4538c942c --- /dev/null +++ b/server/jetty-app/README.md @@ -0,0 +1,201 @@ +# server-jetty-app + +This README is oriented towards getting a server up for local development in a development environment. From a development environment, users can modify source code towards contributing to the project, creating custom capabilities, and more. + +If you wish to use Deephaven from a production environment, which is simpler but source code cannot be modified, see one of the following documents: + +- [Quickstart for Docker](https://deephaven.io/core/docs/tutorials/quickstart/) +- [How to configure the Deephaven native application](https://deephaven.io/core/docs/how-to-guides/configuration/native-application/) + +This README deals with general development for either the Python or Groovy server-side API. For Python-specific development instructions, see the [Python development README](../../py/README.md). + +## Local development + +`./gradlew server-jetty-app:run` will incorporate local Java changes on each start. If you are not frequently changing Java code, see the next section. + +```shell +./gradlew server-jetty-app:run +``` + +## Development with infrequent changes + +To create a more production-like environment, you can create and invoke the start script instead of running via gradle. This is faster if you need to often restart the server without making any changes to Java code (such as Python server development). + +```shell +./gradlew server-jetty-app:installDist # Run after any Java changes +./server/jetty-app/build/install/server-jetty/bin/start +``` + +The specifics of the start script can be found in [unixStartScript.txt](../../buildSrc/src/main/resources/unixStartScript.txt), +which has been adapted from the [gradle java application plugin](https://docs.gradle.org/current/userguide/application_plugin.html). + +### Configuration + +#### Quick + +The following is a list of some "quick" gradle properties that can be set to change the behavior of the Deephaven server: + +* `groovy`: use a groovy console (as opposed to python) +* `anonymous`: use anonymous authentication +* `psk=`: enable pre-shared authentication with a specific `` +* `debug`: enable JVM debug port 5005 +* `devCerts`: use development certificates and port 8443 +* `devMTLS`: use mutual TLS (client) certificates + `devCerts` +* `gcApplication`: enable the GcApplication +* `quiet`: disables bootstrap logging + +These are typically used ad-hoc as part of a gradlew command: + +```shell +./gradlew server-jetty-app:run # Python session (default) +./gradlew server-jetty-app:run -Pdebug # Attach a Java debugger to the Python session on port 5005 +./gradlew server-jetty-app:run -Pgroovy # Groovy session +./gradlew server-jetty-app:run -Pgroovy -Pdebug # Attach a Java debugger to the Groovy session on port 5005 +``` + +The specifics for these quick configurations can be found in [build.gradle](build.gradle). + +#### JVM arguments + +There are a few different ways to configure JVM arguments for development purposes. +The `START_OPTS` environment variable is a user-specific configuration most useful for setting JVM heap size and other related JVM options: + +```shell +START_OPTS="-Xmx12g" ./gradlew server-jetty-app:run # Starts Deephaven with 12gb of heap memory +``` + +The `JAVA_OPTS` environment variable that allows the user to override the Deephaven-recommended JVM arguments +(the Deephaven-recommended arguments may include a specific garbage collector and related tuning parameters). +For example, to use the ZGC generational garbage collector: + +```shell +JAVA_OPTS="-XX:+UseZGC -XX:+ZGenerational" START_OPTS="-Xmx12g" ./gradlew server-jetty-app:run # Starts Deephaven with ZGC generational collector with 12gb of heap memory +``` + +These environmental variables mimic the behavior of the environmental variables from the [native application script](https://deephaven.io/core/docs/how-to-guides/configuration/native-application/#native-application-script). + +These JVM arguments can also be configured through the gradle properties `deephaven.startOpts` and `deephaven.javaOpts` respectively: + +```shell +./gradlew server-jetty-app:run -Pdeephaven.startOpts="-Xmx12g" # Starts Deephaven with 12gb of heap memory +``` + +More commonly though, this provides a mechanism for developers to persist their own Deephaven JVM arguments as gradle properties in `~./gradle/gradle.properties`: + +```properties +# Persistently set Deephaven development to use the ZGC generational collector +deephaven.javaOpts=-XX:+UseZGC -XX:+ZGenerational +``` + +When present, the gradle properties take precedence over the corresponding environment variables. +The specifics for development-related JVM configuration can be found in [io.deephaven.java-toolchain-conventions.gradle](../../buildSrc/src/main/groovy/io.deephaven.java-toolchain-conventions.gradle). + +#### Deephaven properties + +While Deephaven configuration properties can be inherited via JVM system properties (`-Dmy.property=my.value`), +developers may prefer to set persistent configuration properties in the `/deephaven.prop` file. +On Linux, this file is `~/.config/deephaven/deephaven.prop`. +On Mac OS, this file is `~/Library/Application Support/io.Deephaven-Data-Labs.deephaven/deephaven.prop`. + +For example, here is a configuration file that enables anonymous authentication, disables HTTP1, and uses TLS on port 8443: + +```properties +includefiles=dh-defaults.prop + +http.port=8443 +http.http1=false + +ssl.identity.type=privatekey +ssl.identity.certChainPath=/deephaven-core/server/dev-certs/server.chain.crt +ssl.identity.privateKeyPath=/deephaven-core/server/dev-certs/server.key + +AuthHandlers=io.deephaven.auth.AnonymousAuthenticationHandler +``` + +See [config-dir](https://deephaven.io/core/docs/how-to-guides/configuration/native-application/#config-directory) for more information on ``. + +See [config-file](https://deephaven.io/core/docs/how-to-guides/configuration/config-file/) for more information on the configuration file format. + +### Shutdown + +There are multiple ways to shut down the Deephaven server. The easiest is to `ctrl+C` the process. If it's being run in background mode, you can kill it with a `SIGINT`. + +```sh +kill -2 +``` + +### SSL + +By default, the server starts up on all interfaces with plaintext port 10000 (port 443 when SSL is enabled), a token +expiration duration of 5 minutes, a scheduler pool size of 4, and a max inbound message size of 100 MiB. + +To bring up a SSL-enabled server on port 8443 with a development key and certificate, you can run: + +```shell +./gradlew server-jetty-app:run -Pgroovy -PdevCerts +``` + +SSL configuration can be applied manually with the properties "ssl.identity.type", "ssl.identity.certChainPath", +"ssl.identity.privateKeyPath", "ssl.trust.type", and "ssl.trust.path". Furthermore, outbound Deephaven-to-Deephaven +connections can be explicitly configured separately if desired, with the same properties prefixed with "outbound.". +See the javadocs on `io.deephaven.server.jetty.JettyConfig` and `io.deephaven.server.runner.Main.parseSSLConfig` for +more information. + +### SSL examples + +#### Simple + +```properties +ssl.identity.type=privatekey +ssl.identity.certChainPath=server.chain.crt +ssl.identity.privateKeyPath=server.key +``` + +This is a common setup where the server specifies a private key and certificate chain. The certificate can be signed +either by a public CA or an internal CA. + +#### Intranet + +```properties +ssl.identity.type=privatekey +ssl.identity.certChainPath=server.chain.crt +ssl.identity.privateKeyPath=server.key +ssl.trust.type=certs +ssl.trust.path=ca.crt +``` + +This is a common intranet setup where the server additionally specifies a trust certificate. Most often, this will be a +pattern used by organizations with an internal CA. + +Outbound initiated Deephaven-to-Deephaven connections will trust other servers that can be verified via ca.crt or the +JDK trust stores. + +#### Zero-trust / Mutual TLS + +```properties +ssl.identity.type=privatekey +ssl.identity.certChainPath=server.chain.crt +ssl.identity.privateKeyPath=server.key +ssl.trust.type=certs +ssl.trust.path=ca.crt +ssl.clientAuthentication=NEEDED +``` + +This is a setup where the server additionally specifies that mutual TLS is required. This may be used to publicly expose +a server without the need to setup a VPN, or for high security intranet needs. + +Inbound connections need to be verifiable via ca.crt. Outbound initiated Deephaven-to-Deephaven connections will trust +other servers that can be verified via ca.crt or the JDK trust stores. + +#### Outbound + +```properties +outbound.ssl.identity.type=privatekey +outbound.ssl.identity.certChainPath=outbound-identity.chain.crt +outbound.ssl.identity.privateKeyPath=outbound-identity.key +outbound.ssl.trust.type=certs +outbound.ssl.trust.path=outbound-ca.crt +``` + +In all of the above cases, the outbound Deephaven-to-Deephaven connections can be configured separately from the +server's inbound configuration. \ No newline at end of file diff --git a/server/jetty-app/build.gradle b/server/jetty-app/build.gradle new file mode 100644 index 00000000000..4c6cef2a386 --- /dev/null +++ b/server/jetty-app/build.gradle @@ -0,0 +1,140 @@ +plugins { + id 'application' + id 'io.deephaven.project.register' + id 'io.deephaven.optional-server-dependencies' +} + +configurations { + applicationDist +} + +dependencies { + implementation project(':server-jetty') + + runtimeOnly project(':log-to-slf4j') + runtimeOnly project(':logback-print-stream-globals') + runtimeOnly project(':logback-logbuffer') + runtimeOnly libs.logback.classic +} + +distributions { + main { + distributionBaseName = 'server-jetty' + } +} + +def extraJvmArgs = [ + '-server', + '-XshowSettings:vm', +] + +if (hasProperty('groovy')) { + extraJvmArgs += ['-Ddeephaven.console.type=groovy'] +} + +if (!hasProperty('excludeHotspotImpl')) { + extraJvmArgs += ['--add-exports', 'java.management/sun.management=ALL-UNNAMED'] +} + +if (!hasProperty('excludeClockImpl')) { + extraJvmArgs += ['--add-exports', 'java.base/jdk.internal.misc=ALL-UNNAMED'] +} + +// Note: add optional server dependencies to io.deephaven.optional-server-dependencies plugin in buildSrc + +def authHandlers = [] +def authConfigs = ['AuthHandlers'] +if (hasProperty('anonymous')) { + authHandlers += ['io.deephaven.auth.AnonymousAuthenticationHandler'] +} + +if (hasProperty('devCerts') || hasProperty('devMTLS')) { + extraJvmArgs += [ + '-Dhttp.port=8443', + '-Dssl.identity.type=privatekey', + '-Dssl.identity.certChainPath=../dev-certs/server.chain.crt', + '-Dssl.identity.privateKeyPath=../dev-certs/server.key', + ] + if (hasProperty('devMTLS')) { + extraJvmArgs += [ + '-Dssl.trust.type=certs', + '-Dssl.trust.path=../dev-certs/ca.crt', + '-Dssl.clientAuthentication=NEEDED', + ] + authHandlers += ['io.deephaven.authentication.mtls.MTlsAuthenticationHandler'] + dependencies.implementation(dependencies.project(path: ':authentication:example-providers:mtls', configuration:'shadow')) + } +} + +if (hasProperty('debug')) { + extraJvmArgs += ['-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'] +} + +if (hasProperty('debugAutocomplete')) { + extraJvmArgs += ['-Ddeephaven.console.autocomplete.quiet=false'] +} + +if (hasProperty('gcApplication')) { + extraJvmArgs += ['-Dio.deephaven.app.GcApplication.enabled=true'] +} + +if (hasProperty('quiet')) { + extraJvmArgs += ['-Ddeephaven.quiet=true'] +} + +if (hasProperty('psk')) { + authHandlers += ['io.deephaven.authentication.psk.PskAuthenticationHandler'] + if (project.getProperty('psk')) { + // if there is a non-empty value assigned, use that for the key + extraJvmArgs += ["-Dauthentication.psk=${getProperty('psk')}"] + } +} +if (hasProperty('sql-username-password')) { + authHandlers += ['io.deephaven.authentication.sql.BasicSqlAuthenticationHandler'] + extraJvmArgs += [ + '-Dauthentication.basic.sql.jdbc.connection=jdbc:postgresql://localhost:5432/postgres', + '-Dauthentication.basic.sql.jdbc.user=postgres', + '-Dauthentication.basic.sql.jdbc.password=password', + ] + dependencies.implementation(dependencies.project(path: ':authentication:example-providers:sql-username-password', configuration:'shadow')) +} +if (hasProperty('oidc')) { + authHandlers += ['io.deephaven.authentication.oidc.OidcAuthenticationHandler'] + extraJvmArgs += [ + '-Dauthentication.oidc.keycloak.url=http://localhost:6060', + '-Dauthentication.oidc.keycloak.realm=deephaven_core', + '-Dauthentication.oidc.keycloak.clientId=deephaven', + ] + authConfigs += [ + 'authentication.oidc.keycloak.url', + 'authentication.oidc.keycloak.realm', + 'authentication.oidc.keycloak.clientId', + ] + extraJvmArgs += ['-Dauthentication.client.configuration.list=AuthHandlers,authentication.oidc.keycloak.url,authentication.oidc.keycloak.realm,authentication.oidc.keycloak.clientId'] + dependencies.implementation(dependencies.project(path: ':authentication:example-providers:oidc', configuration:'shadow')) +} + +if (!authHandlers.isEmpty()) { + extraJvmArgs += ["-DAuthHandlers=${authHandlers.join(',')}"] +} +extraJvmArgs += ["-Dauthentication.client.configuration.list=${authConfigs.join(',')}"] + +tasks.withType(JavaExec).configureEach { + // This appends to the existing jvm args, so that java-open-nio still takes effect + jvmArgs extraJvmArgs +} + +tasks.withType(CreateStartScripts).configureEach { + defaultJvmOpts += extraJvmArgs +} + +application { + applicationName = 'start' + mainClass = 'io.deephaven.server.jetty.JettyMain' +} + +artifacts { + applicationDist project.tasks.findByName('distTar') +} + +apply plugin: 'io.deephaven.java-open-nio' diff --git a/server/jetty-app/gradle.properties b/server/jetty-app/gradle.properties new file mode 100644 index 00000000000..0ec544952f1 --- /dev/null +++ b/server/jetty-app/gradle.properties @@ -0,0 +1,6 @@ +io.deephaven.project.ProjectType=JAVA_APPLICATION +compilerVersion=17 +runtimeVersion=17 +languageLevel=17 +testRuntimeVersion=17 +testLanguageLevel=17 diff --git a/server/jetty-app/src/main/java/io/deephaven/server/jetty/JettyMain.java b/server/jetty-app/src/main/java/io/deephaven/server/jetty/JettyMain.java new file mode 100644 index 00000000000..9c78b757d59 --- /dev/null +++ b/server/jetty-app/src/main/java/io/deephaven/server/jetty/JettyMain.java @@ -0,0 +1,27 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import io.deephaven.configuration.Configuration; +import io.deephaven.server.runner.MainHelper; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +/** + * The out-of-the-box Deephaven community server. + * + * @see CommunityComponentFactory + */ +public final class JettyMain { + public static void main(String[] args) + throws IOException, InterruptedException, ClassNotFoundException, TimeoutException { + final Configuration configuration = MainHelper.init(args, JettyMain.class); + new CommunityComponentFactory() + .build(configuration) + .getServer() + .run() + .join(); + } +} diff --git a/server/jetty-app/src/main/resources/logback-debug.xml b/server/jetty-app/src/main/resources/logback-debug.xml new file mode 100644 index 00000000000..d9580aae98e --- /dev/null +++ b/server/jetty-app/src/main/resources/logback-debug.xml @@ -0,0 +1,32 @@ + + + + + + + + %d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z', UTC} | %green(%thread) | %highlight(%level) | %yellow(%logger) | %m%n + + + + + + + + + %thread | %logger | %m + + + + + + + + + + + + diff --git a/server/jetty-app/src/main/resources/logback-minimal.xml b/server/jetty-app/src/main/resources/logback-minimal.xml new file mode 100644 index 00000000000..4b1fadb1e55 --- /dev/null +++ b/server/jetty-app/src/main/resources/logback-minimal.xml @@ -0,0 +1,21 @@ + + + + + + + + + %-20.20thread | %-25.25logger{25} | %m + + + + + + + + + + + diff --git a/server/jetty-app/src/main/resources/logback.xml b/server/jetty-app/src/main/resources/logback.xml new file mode 100644 index 00000000000..4ab572fd3f8 --- /dev/null +++ b/server/jetty-app/src/main/resources/logback.xml @@ -0,0 +1,39 @@ + + + + + + + + %d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z', UTC} | %green(%-20.20thread) | %highlight(%5level) | %yellow(%-25.25logger{25}) | %m%n + + + + + + + + %-20.20thread | %-25.25logger{25} | %m + + + + + + + + + + + + + + + + + + + + + diff --git a/server/jetty/build.gradle b/server/jetty/build.gradle new file mode 100644 index 00000000000..33a1f551630 --- /dev/null +++ b/server/jetty/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'java-library' + id 'io.deephaven.project.register' +} + +dependencies { + api project(':authentication') + api project(':authorization') + api project(':server') + api(project(':Integrations')) { + because 'downstream dagger compile' + } + runtimeOnly(project(':web')) + + implementation libs.dagger + annotationProcessor libs.dagger.compiler + + testImplementation libs.dagger + testAnnotationProcessor libs.dagger.compiler + + implementation platform(libs.grpc.bom) + implementation platform(libs.jetty.bom) + implementation platform(libs.jetty.ee10.bom) + + api libs.jakarata.servlet.api + implementation libs.jetty.servlet + implementation libs.jetty.servlets + implementation libs.jetty.webapp + implementation libs.jetty.http2.server + implementation libs.jetty.alpn.server + // TODO(deephaven-core#2506): Support for alternative ALPN implementations + runtimeOnly libs.jetty.alpn.java.server + +// implementation 'io.grpc:grpc-servlet-jakarta' + api(project(':grpc-java:grpc-servlet-jakarta')) { + because 'downstream dagger compile' + } + implementation project(':grpc-java:grpc-servlet-websocket-jakarta') + implementation libs.jetty.websocket.jakarta.server + + compileOnly project(':util-immutables') + annotationProcessor libs.immutables.value + + implementation project(':ssl-kickstart') + implementation libs.sslcontext.kickstart.jetty + + implementation project(':grpc-java:grpc-mtls') + + testImplementation project(':server-test-utils') + testImplementation libs.junit4 + testImplementation libs.assertj + + testRuntimeOnly project(':log-to-slf4j') + testRuntimeOnly libs.slf4j.simple +} + +test.systemProperty "PeriodicUpdateGraph.allowUnitTestMode", false + +apply plugin: 'io.deephaven.java-open-nio' diff --git a/server/jetty/gradle.properties b/server/jetty/gradle.properties new file mode 100644 index 00000000000..8782660c33c --- /dev/null +++ b/server/jetty/gradle.properties @@ -0,0 +1,6 @@ +io.deephaven.project.ProjectType=JAVA_PUBLIC +compilerVersion=17 +runtimeVersion=17 +languageLevel=17 +testRuntimeVersion=17 +testLanguageLevel=17 diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/CacheFilter.java b/server/jetty/src/main/java/io/deephaven/server/jetty/CacheFilter.java new file mode 100644 index 00000000000..deb095b5150 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/CacheFilter.java @@ -0,0 +1,39 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; + +public class CacheFilter implements Filter { + public static final int YEAR_IN_SECONDS = 365 * 24 * 60 * 60; + + @Override + public void doFilter(final ServletRequest request, + final ServletResponse response, + final FilterChain filterChain) + throws IOException, ServletException { + final HttpServletResponse httpResponse = (HttpServletResponse) response; + + // set expiry to one year in the future + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date()); + calendar.add(Calendar.YEAR, 1); + httpResponse.setDateHeader("Expires", calendar.getTime().getTime()); + // Note: immutable tells firefox to never revalidate as data will never change + httpResponse.setHeader("Cache-control", "max-age=" + YEAR_IN_SECONDS + ", public, immutable"); + httpResponse.setHeader("Pragma", ""); + + filterChain.doFilter(request, response); + } + +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/CommunityComponentFactory.java b/server/jetty/src/main/java/io/deephaven/server/jetty/CommunityComponentFactory.java new file mode 100644 index 00000000000..014eaae59eb --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/CommunityComponentFactory.java @@ -0,0 +1,80 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import dagger.Component; +import dagger.Module; +import io.deephaven.configuration.Configuration; +import io.deephaven.server.auth.CommunityAuthorizationModule; +import io.deephaven.server.runner.CommunityDefaultsModule; +import io.deephaven.server.runner.ComponentFactoryBase; + +import javax.inject.Singleton; +import java.io.PrintStream; + +/** + * The out-of-the-box {@link CommunityComponent} factory for the Deephaven community server. + * + *

+ * To use this directly, a main class can be configured as follows: + * + *

+ * public final class MyMainClass {
+ *     public static void main(String[] args)
+ *             throws IOException, InterruptedException, ClassNotFoundException, TimeoutException {
+ *         final Configuration configuration = MainHelper.init(args, MyMainClass.class);
+ *         new CommunityComponentFactory()
+ *                 .build(configuration)
+ *                 .getServer()
+ *                 .run()
+ *                 .join();
+ *     }
+ * }
+ * 
+ * + * Advanced integrators should prefer to create their own component factory that extends {@link ComponentFactoryBase}. + */ +public final class CommunityComponentFactory + extends ComponentFactoryBase { + + @Override + public CommunityComponent build(Configuration configuration, PrintStream out, PrintStream err) { + final JettyConfig jettyConfig = JettyConfig.buildFromConfig(configuration).build(); + return DaggerCommunityComponentFactory_CommunityComponent.builder() + .withOut(out) + .withErr(err) + .withJettyConfig(jettyConfig) + .build(); + } + + /** + * The out-of-the-box community {@link Component}. Includes the {@link CommunityModule}. + */ + @Singleton + @Component(modules = CommunityModule.class) + public interface CommunityComponent extends JettyServerComponent { + + @Component.Builder + interface Builder extends JettyServerComponent.Builder { + } + } + + /** + * The out-of-the-box community {@link Module}. + * + * @see JettyServerModule + * @see CommunityAuthorizationModule + * @see CommunityDefaultsModule + */ + @Module(includes = { + JettyServerModule.class, + JettyClientChannelFactoryModule.class, + CommunityAuthorizationModule.class, + CommunityDefaultsModule.class, + // Implementation note: when / if modules are migrated out of CommunityDefaultsModule, they will need to be + // re-added here. + }) + public interface CommunityModule { + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/ControlledCacheResource.java b/server/jetty/src/main/java/io/deephaven/server/jetty/ControlledCacheResource.java new file mode 100644 index 00000000000..a2ec8763779 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/ControlledCacheResource.java @@ -0,0 +1,108 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import org.eclipse.jetty.util.resource.Resource; + +import java.net.URI; +import java.nio.file.Path; +import java.time.Instant; + +/** + * Simple wrapper around the Jetty Resource type, to grant us control over caching features. The current implementation + * only removes the last-modified value, but a future version could provide a "real" weak/strong etag. + */ +public class ControlledCacheResource extends Resource { + public static ControlledCacheResource wrap(Resource wrapped) { + if (wrapped instanceof ControlledCacheResource) { + return (ControlledCacheResource) wrapped; + } + return new ControlledCacheResource(wrapped); + } + + private final Resource wrapped; + + private ControlledCacheResource(Resource wrapped) { + this.wrapped = wrapped; + } + + @Override + public Path getPath() { + return wrapped.getPath(); + } + + @Override + public boolean isContainedIn(Resource r) { + return wrapped.isContainedIn(r); + } + + @Override + public boolean exists() { + return wrapped.exists(); + } + + @Override + public boolean isDirectory() { + return wrapped.isDirectory(); + } + + @Override + public boolean isReadable() { + return wrapped.isReadable(); + } + + @Override + public Instant lastModified() { + // Always return -1, so that we don't get the build system timestamp. In theory, we could return the app startup + // time as well, so that clients that connect don't need to revalidate quite as often, but this could have other + // side effects such as in load balancing with a short-lived old build against a seconds-older new build. + return Instant.ofEpochMilli(-1); + } + + @Override + public long length() { + return wrapped.length(); + } + + @Override + public URI getURI() { + return wrapped.getURI(); + } + + @Override + public String getName() { + return wrapped.getName(); + } + + @Override + public String getFileName() { + return ""; + } + + @Override + public Resource resolve(String subUriPath) { + return wrap(wrapped.resolve(subUriPath)); + } + + @Override + public String toString() { + // Jetty's CachedContentFactory.CachedHttpContent requires that toString return the underlying URL found on + // disk, or else the mime lookup from content type won't resolve anything. + return wrapped.toString(); + } + + @Override + public int hashCode() { + // As with toString, delegating this to the wrapped instance, just in case there is some specific, expected + // behavior + return wrapped.hashCode(); + } + + @Override + public boolean equals(Object obj) { + // As with toString, delegating this to the wrapped instance, just in case there is some specific, expected + // behavior + return wrapped.equals(obj); + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/CopyHelper.java b/server/jetty/src/main/java/io/deephaven/server/jetty/CopyHelper.java new file mode 100644 index 00000000000..c407f5aca60 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/CopyHelper.java @@ -0,0 +1,81 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import java.io.IOException; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Objects; + +class CopyHelper { + static void copyRecursive(Path src, Path dst, PathMatcher pathMatcher) throws IOException { + copyRecursive(src, dst, pathMatcher, d -> true); + } + + static void copyRecursive(Path src, Path dst, PathMatcher pathMatcher, PathMatcher dirMatcher) throws IOException { + Files.createDirectories(dst.getParent()); + Files.walkFileTree(src, new CopyRecursiveVisitor(src, dst, pathMatcher, dirMatcher)); + } + + private static class CopyRecursiveVisitor extends SimpleFileVisitor { + private final Path src; + private final Path dst; + private final PathMatcher pathMatcher; + private final PathMatcher dirMatcher; + + public CopyRecursiveVisitor(Path src, Path dst, PathMatcher pathMatcher, PathMatcher dirMatcher) { + this.src = Objects.requireNonNull(src); + this.dst = Objects.requireNonNull(dst); + this.pathMatcher = Objects.requireNonNull(pathMatcher); + this.dirMatcher = Objects.requireNonNull(dirMatcher); + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + final Path relativeDir = src.relativize(dir); + if (dirMatcher.matches(relativeDir) || pathMatcher.matches(relativeDir)) { + // Note: toString() necessary for src/dst that don't share the same root FS + Files.copy(dir, dst.resolve(relativeDir.toString()), StandardCopyOption.COPY_ATTRIBUTES); + return FileVisitResult.CONTINUE; + } + return FileVisitResult.SKIP_SUBTREE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + final Path relativeFile = src.relativize(file); + if (pathMatcher.matches(relativeFile)) { + // Note: toString() necessary for src/dst that don't share the same root FS + Files.copy(file, dst.resolve(relativeFile.toString()), StandardCopyOption.COPY_ATTRIBUTES); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + if (exc != null) { + throw exc; + } + final Path relativeDir = src.relativize(dir); + if (!pathMatcher.matches(relativeDir)) { + // If the specific dir does not match as a path (even if it _did_ match as a directory), we + // "optimistically" try and delete it; if the directory is not empty (b/c some subpath matched and was + // copied), the delete will fail. (We could have an alternative impl that keeps track w/ a stack if any + // subpaths matched.) + try { + Files.delete(dir); + } catch (DirectoryNotEmptyException e) { + // ignore + } + } + return FileVisitResult.CONTINUE; + } + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/DropIfModifiedSinceHeader.java b/server/jetty/src/main/java/io/deephaven/server/jetty/DropIfModifiedSinceHeader.java new file mode 100644 index 00000000000..433a86a5327 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/DropIfModifiedSinceHeader.java @@ -0,0 +1,89 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpHeader; + +import java.io.IOException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +/** + * Removes the if-modified-since header from any request that contains it. + */ +public class DropIfModifiedSinceHeader extends HttpFilter { + + @Override + protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) + throws IOException, ServletException { + String ifModifiedSince = HttpHeader.IF_MODIFIED_SINCE.asString(); + if (req.getHeader(ifModifiedSince) != null) { + chain.doFilter(new HeaderRemovingHttpServletRequestWrapper(req, ifModifiedSince), res); + } else { + chain.doFilter(req, res); + } + } + + static class HeaderRemovingHttpServletRequestWrapper extends HttpServletRequestWrapper { + + private final String headerToRemove; + + /** + * Constructs a request object wrapping the given request. + * + * @param request the {@link HttpServletRequest} to be wrapped. + * @throws IllegalArgumentException if the request is null + */ + public HeaderRemovingHttpServletRequestWrapper(HttpServletRequest request, String headerToRemove) { + super(request); + this.headerToRemove = headerToRemove; + } + + @Override + public String getHeader(String name) { + if (name.equalsIgnoreCase(headerToRemove)) { + return null; + } + return super.getHeader(name); + } + + @Override + public long getDateHeader(String name) { + if (name.equalsIgnoreCase(headerToRemove)) { + return -1; + } + return super.getDateHeader(name); + } + + @Override + public int getIntHeader(String name) { + if (name.equalsIgnoreCase(headerToRemove)) { + return -1; + } + return super.getIntHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + if (name.equalsIgnoreCase(headerToRemove)) { + return Collections.emptyEnumeration(); + } + return super.getHeaders(name); + } + + @Override + public Enumeration getHeaderNames() { + List replacement = Collections.list(super.getHeaderNames()); + replacement.remove(headerToRemove); + return Collections.enumeration(replacement); + } + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/GrpcFilter.java b/server/jetty/src/main/java/io/deephaven/server/jetty/GrpcFilter.java new file mode 100644 index 00000000000..04e499b1175 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/GrpcFilter.java @@ -0,0 +1,63 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import io.deephaven.configuration.Configuration; +import io.grpc.servlet.jakarta.ServletAdapter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import javax.inject.Inject; +import java.io.IOException; + +import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_GRPC; + +/** + * Deephaven-core's own handler for registering handlers for various grpc endpoints. + */ +public class GrpcFilter extends HttpFilter { + /** + * Disabling this configuration option allows a server to permit http/1.1 connections. While technically forbidden + * for grpc calls, it could be helpful for extremely lightweight http clients (IoT use cases), or for grpc-web where + * http/1.1 is technically supported. + */ + public static final boolean REQUIRE_HTTP2 = + Configuration.getInstance().getBooleanWithDefault("http.requireHttp2", true); + + private final ServletAdapter grpcAdapter; + + @Inject + public GrpcFilter(ServletAdapter grpcAdapter) { + this.grpcAdapter = grpcAdapter; + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (ServletAdapter.isGrpc(request)) { + // we now know that this request is meant to be grpc, ensure that the underlying http version is not + // 1.1 so that grpc will behave + if (!REQUIRE_HTTP2 || request.getProtocol().equals("HTTP/2.0")) { + grpcAdapter.doPost(request, response); + } else { + // A "clean" implementation of this would use @Internal-annotated types from grpc, which is discouraged. + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType(CONTENT_TYPE_GRPC); + // Status.Code.INTERNAL.valueAscii() is private, using a string literal instead for "13" + response.setHeader("grpc-status", "13"); + response.setHeader("grpc-message", + "The server connection is not using http2, so streams and metadata may not behave as expected. The client may be connecting improperly, or a proxy may be interfering."); + } + } else { + chain.doFilter(request, response); + } + } + + public T create(ServletAdapter.AdapterConstructor constructor) { + return grpcAdapter.otherAdapter(constructor); + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/HomeFilter.java b/server/jetty/src/main/java/io/deephaven/server/jetty/HomeFilter.java new file mode 100644 index 00000000000..1fc40fa913c --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/HomeFilter.java @@ -0,0 +1,33 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class HomeFilter implements Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (request instanceof HttpServletRequest req && response instanceof HttpServletResponse resp) { + final String location; + String queryString = req.getQueryString(); + if (queryString != null) { + location = "/ide/?" + queryString; + } else { + location = "/ide/"; + } + resp.sendRedirect(location); + return; + } + chain.doFilter(request, response); + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java new file mode 100644 index 00000000000..60ba36bbddc --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java @@ -0,0 +1,380 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import io.deephaven.server.browserstreaming.BrowserStreamInterceptor; +import io.deephaven.server.runner.GrpcServer; +import io.deephaven.ssl.config.CiphersIntermediate; +import io.deephaven.ssl.config.ProtocolsIntermediate; +import io.deephaven.ssl.config.SSLConfig; +import io.deephaven.ssl.config.TrustJdk; +import io.deephaven.ssl.config.impl.KickstartUtils; +import io.grpc.InternalStatus; +import io.grpc.internal.GrpcUtil; +import io.grpc.servlet.jakarta.web.GrpcWebFilter; +import io.grpc.servlet.web.websocket.GrpcWebsocket; +import io.grpc.servlet.web.websocket.MultiplexedWebSocketServerStream; +import io.grpc.servlet.web.websocket.WebSocketServerStream; +import jakarta.servlet.DispatcherType; +import jakarta.websocket.Endpoint; +import jakarta.websocket.server.ServerEndpointConfig; +import nl.altindag.ssl.SSLFactory; +import nl.altindag.ssl.jetty.util.JettySslUtils; +import org.apache.arrow.flight.auth.AuthConstants; +import org.apache.arrow.flight.auth2.Auth2Constants; +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.ee10.servlet.DefaultServlet; +import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler; +import org.eclipse.jetty.ee10.servlet.FilterHolder; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler; +import org.eclipse.jetty.ee10.servlets.CrossOriginFilter; +import org.eclipse.jetty.ee10.webapp.WebAppContext; +import org.eclipse.jetty.ee10.websocket.jakarta.common.SessionTracker; +import org.eclipse.jetty.ee10.websocket.jakarta.server.JakartaWebSocketServerContainer; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http2.HTTP2Connection; +import org.eclipse.jetty.http2.HTTP2Session; +import org.eclipse.jetty.http2.RateControl; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.server.ForwardedRequestCustomizer; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.gzip.GzipHandler; +import org.eclipse.jetty.util.ExceptionUtil; +import org.eclipse.jetty.util.component.Graceful; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import static io.grpc.servlet.web.websocket.MultiplexedWebSocketServerStream.GRPC_WEBSOCKETS_MULTIPLEX_PROTOCOL; +import static io.grpc.servlet.web.websocket.WebSocketServerStream.GRPC_WEBSOCKETS_PROTOCOL; +import static org.eclipse.jetty.ee10.servlet.ServletContextHandler.NO_SESSIONS; + +@Singleton +public class JettyBackedGrpcServer implements GrpcServer { + private static final String JS_PLUGINS_PATH_SPEC = "/" + JsPlugins.JS_PLUGINS + "/*"; + + private final Server jetty; + private final boolean websocketsEnabled; + + @Inject + public JettyBackedGrpcServer( + final JettyConfig config, + final GrpcFilter filter, + final JsPlugins jsPlugins) { + jetty = new Server(); + jetty.addConnector(createConnector(jetty, config)); + + final WebAppContext context = + new WebAppContext("/", null, null, null, new ErrorPageErrorHandler(), NO_SESSIONS); + String knownFile = "/ide/index.html"; + URL ide = JettyBackedGrpcServer.class.getResource(knownFile); + Resource jarContents = + context.getResourceFactory().newResource(ide.toExternalForm().replace("!" + knownFile, "!/")); + context.setBaseResource(ControlledCacheResource.wrap(jarContents)); + context.setInitParameter(DefaultServlet.CONTEXT_INIT + "dirAllowed", "false"); + + // Cache all of the appropriate assets folders + for (String appRoot : List.of("/ide/", "/iframe/table/", "/iframe/chart/", "/iframe/widget/")) { + context.addFilter(NoCacheFilter.class, appRoot + "*", EnumSet.noneOf(DispatcherType.class)); + context.addFilter(CacheFilter.class, appRoot + "assets/*", EnumSet.noneOf(DispatcherType.class)); + } + context.addFilter(NoCacheFilter.class, "/jsapi/*", EnumSet.noneOf(DispatcherType.class)); + context.addFilter(DropIfModifiedSinceHeader.class, "/*", EnumSet.noneOf(DispatcherType.class)); + + context.setSecurityHandler(new ConstraintSecurityHandler()); + + // Add an extra filter to redirect from / to /ide/ + context.addFilter(HomeFilter.class, "/", EnumSet.noneOf(DispatcherType.class)); + + // If requested, permit CORS requests + FilterHolder holder = new FilterHolder(CrossOriginFilter.class); + + // Permit all origins + holder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*"); + + // Only support POST - technically gRPC can use GET, but we don't use any of those methods + holder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "POST"); + + // Required request headers for gRPC, gRPC-web, flight, and deephaven + holder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, String.join(",", + // Required for CORS itself to work + HttpHeader.ORIGIN.asString(), + CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, + + // Required for gRPC + GrpcUtil.CONTENT_TYPE_KEY.name(), + GrpcUtil.TIMEOUT_KEY.name(), + + // Optional for gRPC + GrpcUtil.MESSAGE_ENCODING_KEY.name(), + GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY.name(), + GrpcUtil.CONTENT_ENCODING_KEY.name(), + GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY.name(), + + // Required for gRPC-web + "x-grpc-web", + // Optional for gRPC-web + "x-user-agent", + + // Required for Flight auth 1/2 + AuthConstants.TOKEN_NAME, + Auth2Constants.AUTHORIZATION_HEADER, + + // Required for DH gRPC browser bidi stream support + BrowserStreamInterceptor.TICKET_HEADER_NAME, + BrowserStreamInterceptor.SEQUENCE_HEADER_NAME, + BrowserStreamInterceptor.HALF_CLOSE_HEADER_NAME)); + + // Response headers that the browser will need to be able to decode + holder.setInitParameter(CrossOriginFilter.EXPOSED_HEADERS_PARAM, String.join(",", + Auth2Constants.AUTHORIZATION_HEADER, + GrpcUtil.CONTENT_TYPE_KEY.name(), + InternalStatus.CODE_KEY.name(), + InternalStatus.MESSAGE_KEY.name(), + // Not used (yet?), see io.grpc.protobuf.StatusProto + "grpc-status-details-bin")); + + // Add the filter on all requests + context.addFilter(holder, "/*", EnumSet.noneOf(DispatcherType.class)); + + // Handle grpc-web connections, translate to vanilla grpc + context.addFilter(new FilterHolder(new GrpcWebFilter()), "/*", EnumSet.noneOf(DispatcherType.class)); + + // Wire up the provided grpc filter + context.addFilter(new FilterHolder(filter), "/*", EnumSet.noneOf(DispatcherType.class)); + + // Wire up /js-plugins/* + // TODO(deephaven-core#4620): Add js-plugins version-aware caching + context.addFilter(NoCacheFilter.class, JS_PLUGINS_PATH_SPEC, EnumSet.noneOf(DispatcherType.class)); + context.addServlet(servletHolder("js-plugins", jsPlugins.filesystem()), JS_PLUGINS_PATH_SPEC); + + // Set up websockets for grpc-web - depending on configuration, we can register both in case we encounter a + // client using "vanilla" + // grpc-websocket, that can't multiplex all streams on a single socket + if (config.websocketsOrDefault() != JettyConfig.WebsocketsSupport.NONE) { + JakartaWebSocketServletContainerInitializer.configure(context, (servletContext, container) -> { + final Map> endpoints = new HashMap<>(); + if (config.websocketsOrDefault() == JettyConfig.WebsocketsSupport.BOTH + || config.websocketsOrDefault() == JettyConfig.WebsocketsSupport.GRPC_WEBSOCKET) { + endpoints.put(GRPC_WEBSOCKETS_PROTOCOL, () -> filter.create(WebSocketServerStream::new)); + } + if (config.websocketsOrDefault() == JettyConfig.WebsocketsSupport.BOTH + || config.websocketsOrDefault() == JettyConfig.WebsocketsSupport.GRPC_WEBSOCKET_MULTIPLEXED) { + endpoints.put(GRPC_WEBSOCKETS_MULTIPLEX_PROTOCOL, + () -> filter.create(MultiplexedWebSocketServerStream::new)); + } + JakartaWebSocketServerContainer jettyWebsocketContainer = (JakartaWebSocketServerContainer) container; + WebsocketFactory websocketFactory = + new WebsocketFactory(() -> new GrpcWebsocket(endpoints), jettyWebsocketContainer); + jettyWebsocketContainer.addBean(websocketFactory); + container.addEndpoint(ServerEndpointConfig.Builder.create(GrpcWebsocket.class, "/{service}/{method}") + .configurator(new ServerEndpointConfig.Configurator() { + @Override + public T getEndpointInstance(Class endpointClass) { + // noinspection unchecked + return (T) websocketFactory.create(); + } + }) + .subprotocols(new ArrayList<>(endpoints.keySet())) + .build()); + }); + this.websocketsEnabled = true; + } else { + this.websocketsEnabled = false; + } + + // Optionally wrap the webapp in a gzip handler + final Handler handler; + if (config.httpCompressionOrDefault()) { + final GzipHandler gzipHandler = new GzipHandler(); + // The default of 32 bytes seems a bit small. + gzipHandler.setMinGzipSize(1024); + // The GzipHandler documentation says GET is the default, but the constructor shows both GET and POST. + // This should ensure our gRPC messages don't get compressed for now, but we may need to be more explicit in + // the future as gRPC can technically operate over GET. + gzipHandler.setIncludedMethods(HttpMethod.GET.asString()); + // Otherwise, the other defaults seem reasonable. + gzipHandler.setHandler(context); + handler = gzipHandler; + } else { + handler = context; + } + jetty.setHandler(handler); + } + + @Override + public void start() throws IOException { + try { + jetty.start(); + } catch (RuntimeException exception) { + throw exception; + } catch (Exception exception) { + throw new IOException(exception); + } + } + + @Override + public void join() throws InterruptedException { + jetty.join(); + } + + @Override + public void beginShutdown() { + // "start to stop" the jetty container, skipping over websockets, since their Graceful implementation isn't + // very nice. This is roughly the implementation of Graceful.shutdown(Component), except avoiding anything that + // would directly stop a websocket, which instead will be handled later, as part of the actual stop() call tell + // the graceful handlers that we are shutting down. + + // For websockets, since the SessionTracker will instantly stop the socket rather than allow it to finish + // nicely. Instead, when websockets were created, we registered extra graceful beans to shutdown like h2. + // See Server.doStop(), this is roughly the implementation of the first phase of that method, only asking + // Graceful instances to stop, but not stopping connectors or non-graceful components. + + // Note that this would not apply correctly if we used WebSockets for some purpose other than gRPC transport. + Collection gracefuls = jetty.getContainedBeans(Graceful.class); + gracefuls.stream().filter(g -> !(g instanceof SessionTracker)).forEach(Graceful::shutdown); + } + + @Override + public void stopWithTimeout(long timeout, TimeUnit unit) { + Thread shutdownThread = new Thread(() -> { + ExceptionUtil.MultiException exceptions = new ExceptionUtil.MultiException(); + long millis = unit.toMillis(timeout); + + // If websockets are enabled, try to spend part of our shutdown timeout budget on waiting for websockets, as + // in beginShutdown. + if (websocketsEnabled && millis > 250) { + // shut down everything except the websockets themselves with half our timeout + millis /= 2; + + // Collect the same beans we gracefully stopped before (or, if we didn't already start a graceful + // shutdown, this is the first attempt) + Collection gracefuls = jetty.getContainedBeans(Graceful.class); + try { + CompletableFuture.allOf(gracefuls.stream().filter(g -> !(g instanceof SessionTracker)) + .map(Graceful::shutdown).toArray(CompletableFuture[]::new)) + .get(millis, TimeUnit.MILLISECONDS); + } catch (Exception e) { + exceptions.add(e); + } + } + + // regardless of failures so far, continue shutdown with remaining budget. This will end all websockets + // right away. + try { + jetty.setStopTimeout(millis); + jetty.stop(); + exceptions.ifExceptionThrow(); + } catch (Exception exception) { + exceptions.add(exception); + } + exceptions.ifExceptionThrowRuntime(); + }); + shutdownThread.start(); + } + + @Override + public int getPort() { + return ((ServerConnector) jetty.getConnectors()[0]).getLocalPort(); + } + + private static ServerConnector createConnector(Server server, JettyConfig config) { + // https://www.eclipse.org/jetty/documentation/jetty-11/programming-guide/index.html#pg-server-http-connector-protocol-http2-tls + final HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.addCustomizer(new ForwardedRequestCustomizer()); + final HttpConnectionFactory http11 = config.http1OrDefault() ? new HttpConnectionFactory(httpConfig) : null; + final ServerConnector serverConnector; + if (config.ssl().isPresent()) { + httpConfig.addCustomizer(new SecureRequestCustomizer(config.sniHostCheck())); + final HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfig); + h2.setRateControlFactory(new RateControl.Factory() {}); + + final ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + alpn.setDefaultProtocol(http11 != null ? http11.getProtocol() : h2.getProtocol()); + // The Jetty server is getting intermediate setup by default if none are configured. This is most similar to + // how the Netty servers gets setup by default via GrpcSslContexts. + final SSLConfig sslConfig = config.ssl().get() + .orTrust(TrustJdk.of()) + .orProtocols(ProtocolsIntermediate.of()) + .orCiphers(CiphersIntermediate.of()); + final SSLFactory kickstart = KickstartUtils.create(sslConfig); + final SslContextFactory.Server jetty = JettySslUtils.forServer(kickstart); + final SslConnectionFactory tls = new SslConnectionFactory(jetty, alpn.getProtocol()); + if (http11 != null) { + serverConnector = new ServerConnector(server, tls, alpn, h2, http11); + } else { + serverConnector = new ServerConnector(server, tls, alpn, h2); + } + } else { + final HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig); + h2c.setRateControlFactory(new RateControl.Factory() {}); + if (http11 != null) { + serverConnector = new ServerConnector(server, http11, h2c); + } else { + serverConnector = new ServerConnector(server, h2c); + } + } + config.host().ifPresent(serverConnector::setHost); + serverConnector.setPort(config.port()); + + // Give connections extra time to shutdown, since we have an explicit server shutdown + serverConnector.setShutdownIdleTimeout(serverConnector.getIdleTimeout()); + + // Override the h2 stream timeout with a specified value + serverConnector.addEventListener(new Connection.Listener() { + @Override + public void onOpened(Connection connection) { + if (connection instanceof HTTP2ServerConnection) { + HTTP2Session session = (HTTP2Session) ((HTTP2Connection) connection).getSession(); + session.setStreamIdleTimeout(config.http2StreamIdleTimeoutOrDefault()); + } + } + + @Override + public void onClosed(Connection connection) { + + } + }); + + return serverConnector; + } + + private static ServletHolder servletHolder(String name, URI filesystemUri) { + final ServletHolder jsPlugins = new ServletHolder(name, DefaultServlet.class); + // Note, the URI needs explicitly be parseable as a directory URL ending in "!/", a requirement of the jetty + // resource creation implementation, see + // org.eclipse.jetty.util.resource.Resource.newResource(java.lang.String, boolean) + jsPlugins.setInitParameter("resourceBase", filesystemUri.toString()); + jsPlugins.setInitParameter("pathInfoOnly", "true"); + jsPlugins.setInitParameter("dirAllowed", "false"); + jsPlugins.setAsyncSupported(true); + return jsPlugins; + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyCertInterceptor.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyCertInterceptor.java new file mode 100644 index 00000000000..eed4cc1e35c --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyCertInterceptor.java @@ -0,0 +1,22 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import io.deephaven.grpc.AbstractMtlsClientCertificateInterceptor; +import io.grpc.ServerCall; +import io.grpc.servlet.jakarta.GrpcServlet; + +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Optional; + +/** + * Jetty pre-packages the certificates for us, no need to convert them + */ +public class JettyCertInterceptor extends AbstractMtlsClientCertificateInterceptor { + @Override + protected Optional> getTransportCertificates(ServerCall call) { + return Optional.ofNullable(call.getAttributes().get(GrpcServlet.MTLS_CERTIFICATE_KEY)); + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyClientChannelFactoryModule.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyClientChannelFactoryModule.java new file mode 100644 index 00000000000..3646d125631 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyClientChannelFactoryModule.java @@ -0,0 +1,26 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import dagger.Module; +import dagger.Provides; +import io.deephaven.client.impl.BarrageSessionFactoryConfig; +import io.deephaven.server.session.ClientChannelFactoryModule; +import io.deephaven.server.session.ClientChannelFactoryModule.UserAgent; +import io.deephaven.server.session.SslConfigModule; + +import java.util.List; + +@Module(includes = { + ClientChannelFactoryModule.class, + SslConfigModule.class +}) +public interface JettyClientChannelFactoryModule { + + @Provides + @UserAgent + static String providesUserAgent() { + return BarrageSessionFactoryConfig.userAgent(List.of("deephaven-server-jetty")); + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyConfig.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyConfig.java new file mode 100644 index 00000000000..109cd2371d3 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyConfig.java @@ -0,0 +1,227 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import io.deephaven.annotations.BuildableStyle; +import io.deephaven.configuration.Configuration; +import io.deephaven.server.config.ServerConfig; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; +import org.immutables.value.Value.Style; + +import javax.annotation.Nullable; +import java.util.OptionalLong; + +/** + * The jetty server configuration. + */ +@Immutable +@BuildableStyle +// Need to let EmbeddedServer overwrite builder from python +@Style(strictBuilder = false) +public abstract class JettyConfig implements ServerConfig { + + public static final int DEFAULT_SSL_PORT = 443; + public static final int DEFAULT_PLAINTEXT_PORT = 10000; + public static final String HTTP_WEBSOCKETS = "http.websockets"; + public static final String HTTP_HTTP1 = "http.http1"; + public static final String HTTP_STREAM_TIMEOUT = "http2.stream.idleTimeoutMs"; + public static final String HTTP_COMPRESSION = "http.compression"; + public static final String SNI_HOST_CHECK = "https.sniHostCheck"; + + /** + * Values to indicate what kind of websocket support should be offered. + */ + public enum WebsocketsSupport { + + /** + * Disable all websockets. Recommended for use with https, including behind a proxy that will offer its own + * https. + */ + NONE, + /** + * Establish one websocket per grpc stream (including unary calls). Compatible with the websocket client + * provided by improbable-eng/grpc-web, but not + * recommended. + */ + GRPC_WEBSOCKET, + /** + * Allows reuse of a single websocket for many grpc streams, even between services. This reduces latency by + * avoiding a fresh websocket handshake per rpc. + */ + GRPC_WEBSOCKET_MULTIPLEXED, + + /** + * Enables both {@link #GRPC_WEBSOCKET} and {@link #GRPC_WEBSOCKET_MULTIPLEXED}, letting the client specify + * which to use via websocket subprotocols. + */ + BOTH; + } + + public static Builder builder() { + return ImmutableJettyConfig.builder(); + } + + /** + * The default configuration is suitable for local development purposes. It inherits all of the defaults, which are + * documented on each individual method. In brief, the default server starts up on all interfaces with plaintext + * port {@value DEFAULT_PLAINTEXT_PORT}, a scheduler pool size of {@value DEFAULT_SCHEDULER_POOL_SIZE}, and a max + * inbound message size of {@value DEFAULT_MAX_INBOUND_MESSAGE_SIZE_MiB} MiB. + */ + public static JettyConfig defaultConfig() { + return builder().build(); + } + + /** + * Parses the configuration values into the appropriate builder methods via + * {@link ServerConfig#buildFromConfig(ServerConfig.Builder, Configuration)}. + * + *

+ * Additionally, parses the property {@value HTTP_WEBSOCKETS} into {@link Builder#websockets(WebsocketsSupport)}, + * {@value HTTP_HTTP1} into {@link Builder#http1(Boolean)}, {@value HTTP_STREAM_TIMEOUT} into + * {@link Builder#http2StreamIdleTimeout(long)}, and {@value HTTP_COMPRESSION} into + * {@link Builder#httpCompression(Boolean)} + * + * @param config the config + * @return the builder + */ + public static Builder buildFromConfig(Configuration config) { + final Builder builder = ServerConfig.buildFromConfig(builder(), config); + String httpWebsockets = config.getStringWithDefault(HTTP_WEBSOCKETS, null); + String httpHttp1 = config.getStringWithDefault(HTTP_HTTP1, null); + String httpCompression = config.getStringWithDefault(HTTP_COMPRESSION, null); + String sniHostCheck = config.getStringWithDefault(SNI_HOST_CHECK, null); + String h2StreamIdleTimeout = config.getStringWithDefault(HTTP_STREAM_TIMEOUT, null); + if (httpWebsockets != null) { + switch (httpWebsockets.toLowerCase()) { + case "true":// backwards compatible + case "both": + builder.websockets(WebsocketsSupport.BOTH); + break; + case "grpc-websockets": + builder.websockets(WebsocketsSupport.GRPC_WEBSOCKET); + break; + case "grpc-websockets-multiplex": + builder.websockets(WebsocketsSupport.GRPC_WEBSOCKET_MULTIPLEXED); + break; + default: + // backwards compatible, either "false" or "none" or anything else + builder.websockets(WebsocketsSupport.NONE); + } + } + if (httpHttp1 != null) { + builder.http1(Boolean.parseBoolean(httpHttp1)); + } + if (h2StreamIdleTimeout != null) { + builder.http2StreamIdleTimeout(Long.parseLong(h2StreamIdleTimeout)); + } + if (httpCompression != null) { + builder.httpCompression(Boolean.parseBoolean(httpCompression)); + } + if (sniHostCheck != null) { + builder.sniHostCheck(Boolean.parseBoolean(sniHostCheck)); + } + return builder; + } + + /** + * The port. Defaults to {@value DEFAULT_SSL_PORT} if {@link #ssl()} is present, otherwise defaults to + * {@value DEFAULT_PLAINTEXT_PORT}. + */ + @Default + public int port() { + return ssl().isPresent() ? DEFAULT_SSL_PORT : DEFAULT_PLAINTEXT_PORT; + } + + /** + * Include websockets. + */ + @Nullable + public abstract WebsocketsSupport websockets(); + + /** + * Include HTTP/1.1. + */ + @Nullable + public abstract Boolean http1(); + + /** + * Include sniHostCheck. + */ + @Default + public boolean sniHostCheck() { + return true; + } + + public abstract OptionalLong http2StreamIdleTimeout(); + + /** + * Include HTTP compression. + */ + @Nullable + public abstract Boolean httpCompression(); + + /** + * How long can a stream be idle in milliseconds before it should be shut down. Non-positive values disable this + * feature. Default is zero. + */ + public long http2StreamIdleTimeoutOrDefault() { + return http2StreamIdleTimeout().orElse(0); + } + + /** + * Returns {@link #websockets()} if explicitly set. If {@link #proxyHint()} is {@code true}, returns {@code false}. + * Otherwise, defaults to {@code true} when {@link #ssl()} is empty, and {@code false} when {@link #ssl()} is + * present. + */ + public final WebsocketsSupport websocketsOrDefault() { + final WebsocketsSupport websockets = websockets(); + if (websockets != null) { + return websockets; + } + if (Boolean.TRUE.equals(proxyHint())) { + return WebsocketsSupport.NONE; + } + return ssl().isEmpty() ? WebsocketsSupport.BOTH : WebsocketsSupport.NONE; + } + + /** + * Returns {@link #http1()} if explicitly set. If {@link #proxyHint()} is {@code true}, returns {@code false}. + * Otherwise, defaults to {@code true}. This may become more strict in the future, see + * #2787). + */ + public final boolean http1OrDefault() { + final Boolean http1 = http1(); + if (http1 != null) { + return http1; + } + if (Boolean.TRUE.equals(proxyHint())) { + return false; + } + // TODO(deephaven-core#2787): OS / browser testing to determine minimum viable HTTP version + // return ssl().isEmpty(); + return true; + } + + /** + * Returns {@link #httpCompression()} if explicitly set, otherwise returns {@code true}. + */ + public final boolean httpCompressionOrDefault() { + final Boolean httpCompression = httpCompression(); + return httpCompression == null || httpCompression; + } + + public interface Builder extends ServerConfig.Builder { + + Builder websockets(WebsocketsSupport websockets); + + Builder http1(Boolean http1); + + Builder httpCompression(Boolean httpCompression); + + Builder http2StreamIdleTimeout(long timeoutInMillis); + + Builder sniHostCheck(boolean sniHostCheck); + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerComponent.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerComponent.java new file mode 100644 index 00000000000..eeb756a8f1b --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerComponent.java @@ -0,0 +1,17 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import dagger.BindsInstance; +import io.deephaven.server.runner.DeephavenApiServerComponent; + +public interface JettyServerComponent extends DeephavenApiServerComponent { + + interface Builder, Component extends JettyServerComponent> + extends DeephavenApiServerComponent.Builder { + + @BindsInstance + Self withJettyConfig(JettyConfig config); + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerModule.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerModule.java new file mode 100644 index 00000000000..ce105435c90 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyServerModule.java @@ -0,0 +1,83 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import io.deephaven.plugin.js.JsPluginRegistration; +import io.deephaven.server.config.ServerConfig; +import io.deephaven.server.runner.GrpcServer; +import io.grpc.BindableService; +import io.grpc.ServerInterceptor; +import io.grpc.servlet.jakarta.ServletAdapter; +import io.grpc.servlet.jakarta.ServletServerBuilder; + +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +import static io.grpc.internal.GrpcUtil.getThreadFactory; + +@Module +public interface JettyServerModule { + + @Binds + GrpcServer bindServer(JettyBackedGrpcServer jettyBackedGrpcServer); + + @Binds + ServerConfig bindsServerConfig(JettyConfig serverConfig); + + @Provides + static ServletAdapter provideGrpcServletAdapter( + final @Named("grpc.maxInboundMessageSize") int maxMessageSize, + final Set services, + final Set interceptors) { + final ServletServerBuilder serverBuilder = new ServletServerBuilder(); + services.forEach(serverBuilder::addService); + interceptors.forEach(serverBuilder::intercept); + + // create a custom executor service, just like grpc would use, so that grpc doesn't shut it down ahead + // of when we are ready + // We don't use newSingleThreadScheduledExecutor because it doesn't return a + // ScheduledThreadPoolExecutor. + ScheduledThreadPoolExecutor service = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool( + 1, getThreadFactory("grpc-timer-%d", true)); + + // If there are long timeouts that are cancelled, they will not actually be removed from + // the executors queue. This forces immediate removal upon cancellation to avoid a + // memory leak. + service.setRemoveOnCancelPolicy(true); + + ScheduledExecutorService executorService = Executors.unconfigurableScheduledExecutorService(service); + + serverBuilder.scheduledExecutorService(executorService); + + serverBuilder.maxInboundMessageSize(maxMessageSize); + + serverBuilder.directExecutor(); + + serverBuilder.intercept(new JettyCertInterceptor()); + + return serverBuilder.buildServletAdapter(); + } + + @Binds + JsPluginRegistration bindJsPlugins(JsPlugins plugins); + + @Provides + @Singleton + static JsPlugins providesJsPluginRegistration() { + try { + return JsPlugins.create(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JsPlugins.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JsPlugins.java new file mode 100644 index 00000000000..40017a27737 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JsPlugins.java @@ -0,0 +1,43 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import io.deephaven.plugin.js.JsPlugin; +import io.deephaven.plugin.js.JsPluginRegistration; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.util.Objects; + +/** + * Jetty-specific implementation of {@link JsPluginRegistration} to collect plugins and advertise their contents to + * connecting client. + */ +public class JsPlugins implements JsPluginRegistration { + static final String JS_PLUGINS = "js-plugins"; + + public static JsPlugins create() throws IOException { + return new JsPlugins(JsPluginsZipFilesystem.create()); + } + + private final JsPluginsZipFilesystem zipFs; + + private JsPlugins(JsPluginsZipFilesystem zipFs) { + this.zipFs = Objects.requireNonNull(zipFs); + } + + public URI filesystem() { + return zipFs.filesystem(); + } + + @Override + public void register(JsPlugin jsPlugin) { + try { + zipFs.add(jsPlugin); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JsPluginsZipFilesystem.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JsPluginsZipFilesystem.java new file mode 100644 index 00000000000..9e8dedded60 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JsPluginsZipFilesystem.java @@ -0,0 +1,117 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import io.deephaven.configuration.CacheDir; +import io.deephaven.plugin.js.JsPlugin; +import io.deephaven.server.plugin.js.JsPluginManifest; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static io.deephaven.server.jetty.Json.OBJECT_MAPPER; +import static io.deephaven.server.plugin.js.JsPluginManifest.MANIFEST_JSON; + +class JsPluginsZipFilesystem { + private static final String ZIP_ROOT = "/"; + + /** + * Creates a new js plugins instance with a temporary zip filesystem. + * + * @return the js plugins + * @throws IOException if an I/O exception occurs + */ + public static JsPluginsZipFilesystem create() throws IOException { + final Path tempDir = + Files.createTempDirectory(CacheDir.get(), "." + JsPluginsZipFilesystem.class.getSimpleName()); + tempDir.toFile().deleteOnExit(); + final Path fsZip = tempDir.resolve("deephaven-js-plugins.zip"); + fsZip.toFile().deleteOnExit(); + final URI uri = URI.create(String.format("jar:%s!/", fsZip.toUri())); + final JsPluginsZipFilesystem jsPlugins = new JsPluginsZipFilesystem(uri); + jsPlugins.init(); + return jsPlugins; + } + + private final URI filesystem; + private final List plugins; + + private JsPluginsZipFilesystem(URI filesystem) { + this.filesystem = Objects.requireNonNull(filesystem); + this.plugins = new ArrayList<>(); + } + + public URI filesystem() { + return filesystem; + } + + public synchronized void add(JsPlugin plugin) throws IOException { + checkExisting(plugin.name()); + // TODO(deephaven-core#3005): js-plugins checksum-based caching + // Note: FileSystem#close is necessary to write out contents for ZipFileSystem + try (final FileSystem fs = FileSystems.newFileSystem(filesystem, Map.of())) { + final Path manifestRoot = manifestRoot(fs); + final Path dstPath = manifestRoot.resolve(plugin.name()); + // This is using internal knowledge that paths() must be PathsInternal and extends PathsMatcher. + final PathMatcher pathMatcher = (PathMatcher) plugin.paths(); + // If listing and traversing the contents of development directories (and skipping the copy) becomes + // too expensive, we can add logic here wrt PathsInternal/PathsPrefix to specify a dirMatcher. Or, + // properly route directly from the filesystem via Jetty. + CopyHelper.copyRecursive(plugin.path(), dstPath, pathMatcher); + plugins.add(plugin); + writeManifest(fs); + } + } + + private void checkExisting(String name) { + for (JsPlugin existing : plugins) { + if (name.equals(existing.name())) { + // TODO(deephaven-core#3048): Improve JS plugin support around plugins with conflicting names + throw new IllegalArgumentException(String.format( + "js plugin with name '%s' already exists. See https://github.com/deephaven/deephaven-core/issues/3048", + existing.name())); + } + } + } + + private synchronized void init() throws IOException { + // Note: FileSystem#close is necessary to write out contents for ZipFileSystem + try (final FileSystem fs = FileSystems.newFileSystem(filesystem, Map.of("create", "true"))) { + writeManifest(fs); + } + } + + private void writeManifest(FileSystem fs) throws IOException { + final Path manifestJson = manifestRoot(fs).resolve(MANIFEST_JSON); + final Path manifestJsonTmp = manifestJson.resolveSibling(manifestJson.getFileName().toString() + ".tmp"); + // jackson impl does buffering internally + try (final OutputStream out = Files.newOutputStream(manifestJsonTmp)) { + OBJECT_MAPPER.writeValue(out, manifest()); + out.flush(); + } + Files.move(manifestJsonTmp, manifestJson, + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.COPY_ATTRIBUTES, + StandardCopyOption.ATOMIC_MOVE); + } + + private JsPluginManifest manifest() { + return JsPluginManifest.from(plugins); + } + + private static Path manifestRoot(FileSystem fs) { + return fs.getPath(ZIP_ROOT); + } +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/Json.java b/server/jetty/src/main/java/io/deephaven/server/jetty/Json.java new file mode 100644 index 00000000000..8ff9b2e079e --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/Json.java @@ -0,0 +1,12 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +class Json { + static final ObjectMapper OBJECT_MAPPER = + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/NoCacheFilter.java b/server/jetty/src/main/java/io/deephaven/server/jetty/NoCacheFilter.java new file mode 100644 index 00000000000..21d0118b69b --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/NoCacheFilter.java @@ -0,0 +1,35 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class NoCacheFilter implements Filter { + @Override + public void doFilter(final ServletRequest request, + final ServletResponse response, + final FilterChain filterChain) + throws IOException, ServletException { + final HttpServletResponse httpResponse = (HttpServletResponse) response; + + // set expiry to back in the past (makes us a bad candidate for caching) + httpResponse.setDateHeader("Expires", 0); + // HTTP 1.0 (disable caching) + httpResponse.setHeader("Pragma", "no-cache"); + // HTTP 1.1 (disable caching of any kind) + // HTTP 1.1 'pre-check=0, post-check=0' => (Internet Explorer should always check) + // Note: no-store is not included here as it will disable offline application storage on Firefox + httpResponse.setHeader("Cache-control", "no-cache, must-revalidate, pre-check=0, post-check=0"); + + filterChain.doFilter(request, response); + } + +} diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/WebsocketFactory.java b/server/jetty/src/main/java/io/deephaven/server/jetty/WebsocketFactory.java new file mode 100644 index 00000000000..591e33ee6b5 --- /dev/null +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/WebsocketFactory.java @@ -0,0 +1,73 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import io.grpc.servlet.web.websocket.GrpcWebsocket; +import io.grpc.servlet.web.websocket.MultiplexedWebSocketServerStream; +import jakarta.websocket.Endpoint; +import org.eclipse.jetty.ee10.websocket.jakarta.server.JakartaWebSocketServerContainer; +import org.eclipse.jetty.util.component.Graceful; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import static io.grpc.servlet.web.websocket.MultiplexedWebSocketServerStream.GRACEFUL_CLOSE; + +/** + * Helper class to bridge the gap between Jetty's Graceful interface and the jakarta implementation of the supported + * grpc websocket transports. + *

+ *

+ * This type is not an Endpoint, so only one instance of this can create and shutdown many endpoints. + */ +public class WebsocketFactory implements Graceful { + private final AtomicReference> shutdown = new AtomicReference<>(); + + private final Supplier factory; + private final JakartaWebSocketServerContainer jettyWebsocketContainer; + + public WebsocketFactory(Supplier factory, JakartaWebSocketServerContainer jettyWebsocketContainer) { + this.factory = factory; + this.jettyWebsocketContainer = jettyWebsocketContainer; + } + + public Endpoint create() { + return factory.get(); + } + + @Override + public CompletableFuture shutdown() { + // Modeled after AbstractHTTP2ServerConnectionFactory.HTTP2SessionContainer.shutdown() + CompletableFuture result = new CompletableFuture<>(); + // Simply by setting the shutdown, we don't allow new endpoint instances to be created + if (shutdown.compareAndSet(null, result)) { + // iterate created transports, and if we can, prevent new streams + CompletableFuture.allOf( + jettyWebsocketContainer.getOpenSessions().stream() + .map(s -> (MultiplexedWebSocketServerStream.GracefulClose) s.getUserProperties() + .get(GRACEFUL_CLOSE)) + .map(MultiplexedWebSocketServerStream.GracefulClose::get) + .filter(Objects::nonNull) + .toArray(CompletableFuture[]::new)) + .whenComplete((success, throwable) -> { + if (throwable == null) { + // When all clients have acknowledged, complete + result.complete(success); + } else { + result.completeExceptionally(throwable); + } + }); + return result; + } else { + return shutdown.get(); + } + } + + @Override + public boolean isShutdown() { + return shutdown.get() != null; + } +} diff --git a/server/jetty/src/test/java/io/deephaven/server/jetty/JettyFlightRoundTripTest.java b/server/jetty/src/test/java/io/deephaven/server/jetty/JettyFlightRoundTripTest.java new file mode 100644 index 00000000000..4006ae4f9eb --- /dev/null +++ b/server/jetty/src/test/java/io/deephaven/server/jetty/JettyFlightRoundTripTest.java @@ -0,0 +1,196 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import io.deephaven.server.jetty.js.Example123Registration; +import io.deephaven.server.jetty.js.Sentinel; +import io.deephaven.server.plugin.js.JsPluginsManifestRegistration; +import io.deephaven.server.plugin.js.JsPluginsNpmPackageRegistration; +import io.deephaven.server.runner.ExecutionContextUnitTestModule; +import io.deephaven.server.test.FlightMessageRoundTripTest; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.Test; + +import javax.inject.Singleton; +import java.nio.file.Path; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JettyFlightRoundTripTest extends FlightMessageRoundTripTest { + + @Module + public interface JettyTestConfig { + @Provides + static JettyConfig providesJettyConfig() { + return JettyConfig.builder() + .port(0) + .tokenExpire(Duration.of(5, ChronoUnit.MINUTES)) + .build(); + } + } + + @Singleton + @Component(modules = { + ExecutionContextUnitTestModule.class, + FlightTestModule.class, + JettyServerModule.class, + JettyTestConfig.class, + }) + public interface JettyTestComponent extends TestComponent { + } + + @Override + protected TestComponent component() { + return DaggerJettyFlightRoundTripTest_JettyTestComponent.create(); + } + + @Test + public void jsPlugins() throws Exception { + // Note: JettyFlightRoundTripTest is not the most minimal / appropriate bootstrapping for this test, but it is + // the most convenient since it has all of the necessary prerequisites + new Example123Registration().registerInto(component.registration()); + testJsPluginExamples(false, true, true); + } + + @Test + public void jsPluginsFromManifest() throws Exception { + // Note: JettyFlightRoundTripTest is not the most minimal / appropriate bootstrapping for this test, but it is + // the most convenient since it has all of the necessary prerequisites + final Path manifestRoot = Path.of(Sentinel.class.getResource("examples").toURI()); + new JsPluginsManifestRegistration(manifestRoot) + .registerInto(component.registration()); + testJsPluginExamples(false, false, true); + } + + @Test + public void jsPluginsFromNpmPackages() throws Exception { + // Note: JettyFlightRoundTripTest is not the most minimal / appropriate bootstrapping for this test, but it is + // the most convenient since it has all of the necessary prerequisites + final Path example1Root = Path.of(Sentinel.class.getResource("examples/@deephaven_test/example1").toURI()); + final Path example2Root = Path.of(Sentinel.class.getResource("examples/@deephaven_test/example2").toURI()); + // example3 is *not* a npm package, no package.json. + new JsPluginsNpmPackageRegistration(example1Root) + .registerInto(component.registration()); + new JsPluginsNpmPackageRegistration(example2Root) + .registerInto(component.registration()); + testJsPluginExamples(true, true, false); + } + + private void testJsPluginExamples(boolean example1IsLimited, boolean example2IsLimited, boolean hasExample3) + throws Exception { + final HttpClient client = new HttpClient(); + client.start(); + try { + if (hasExample3) { + manifestTest123(client); + } else { + manifestTest12(client); + } + example1Tests(client, example1IsLimited); + example2Tests(client, example2IsLimited); + if (hasExample3) { + example3Tests(client); + } + } finally { + client.stop(); + } + } + + private void manifestTest12(HttpClient client) throws InterruptedException, TimeoutException, ExecutionException { + final ContentResponse manifestResponse = get(client, "js-plugins/manifest.json"); + assertOk(manifestResponse, "application/json", + "{\"plugins\":[{\"name\":\"@deephaven_test/example1\",\"version\":\"0.1.0\",\"main\":\"dist/index.js\"},{\"name\":\"@deephaven_test/example2\",\"version\":\"0.2.0\",\"main\":\"dist/index.js\"}]}"); + } + + private void manifestTest123(HttpClient client) throws InterruptedException, TimeoutException, ExecutionException { + final ContentResponse manifestResponse = get(client, "js-plugins/manifest.json"); + assertOk(manifestResponse, "application/json", + "{\"plugins\":[{\"name\":\"@deephaven_test/example1\",\"version\":\"0.1.0\",\"main\":\"dist/index.js\"},{\"name\":\"@deephaven_test/example2\",\"version\":\"0.2.0\",\"main\":\"dist/index.js\"},{\"name\":\"@deephaven_test/example3\",\"version\":\"0.3.0\",\"main\":\"index.js\"}]}"); + } + + private void example1Tests(HttpClient client, boolean isLimited) + throws InterruptedException, TimeoutException, ExecutionException { + if (isLimited) { + assertThat(get(client, "js-plugins/@deephaven_test/example1/package.json").getStatus()) + .isEqualTo(HttpStatus.NOT_FOUND_404); + } else { + assertOk(get(client, "js-plugins/@deephaven_test/example1/package.json"), + "application/json", + "{\"name\":\"@deephaven_test/example1\",\"version\":\"0.1.0\",\"main\":\"dist/index.js\",\"files\":[\"dist\"]}"); + } + + assertOk( + get(client, "js-plugins/@deephaven_test/example1/dist/index.js"), + "text/javascript", + "// example1/dist/index.js"); + + assertOk( + get(client, "js-plugins/@deephaven_test/example1/dist/index2.js"), + "text/javascript", + "// example1/dist/index2.js"); + } + + private void example2Tests(HttpClient client, boolean isLimited) + throws InterruptedException, TimeoutException, ExecutionException { + if (isLimited) { + assertThat(get(client, "js-plugins/@deephaven_test/example2/package.json").getStatus()) + .isEqualTo(HttpStatus.NOT_FOUND_404); + } else { + assertOk(get(client, "js-plugins/@deephaven_test/example2/package.json"), + "application/json", + "{\"name\":\"@deephaven_test/example2\",\"version\":\"0.2.0\",\"main\":\"dist/index.js\",\"files\":[\"dist\"]}"); + } + + assertOk( + get(client, "js-plugins/@deephaven_test/example2/dist/index.js"), + "text/javascript", + "// example2/dist/index.js"); + + assertOk( + get(client, "js-plugins/@deephaven_test/example2/dist/index2.js"), + "text/javascript", + "// example2/dist/index2.js"); + } + + private void example3Tests(HttpClient client) throws InterruptedException, TimeoutException, ExecutionException { + assertOk( + get(client, "js-plugins/@deephaven_test/example3/index.js"), + "text/javascript", + "// example3/index.js"); + } + + private ContentResponse get(HttpClient client, String path) + throws InterruptedException, TimeoutException, ExecutionException { + return client + .newRequest("localhost", localPort) + .path(path) + .method(HttpMethod.GET) + .send(); + } + + private static void assertOk(ContentResponse response, String contentType, String expected) { + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK_200); + assertThat(response.getMediaType()).isEqualTo(contentType); + assertThat(response.getContentAsString()).isEqualTo(expected); + assertNoCache(response); + } + + private static void assertNoCache(ContentResponse response) { + final HttpFields headers = response.getHeaders(); + assertThat(headers.getDateField("Expires")).isEqualTo(0); + assertThat(headers.get("Pragma")).isEqualTo("no-cache"); + assertThat(headers.get("Cache-control")).isEqualTo("no-cache, must-revalidate, pre-check=0, post-check=0"); + } +} diff --git a/server/jetty/src/test/java/io/deephaven/server/jetty/js/Example123Registration.java b/server/jetty/src/test/java/io/deephaven/server/jetty/js/Example123Registration.java new file mode 100644 index 00000000000..b88294bd7dd --- /dev/null +++ b/server/jetty/src/test/java/io/deephaven/server/jetty/js/Example123Registration.java @@ -0,0 +1,68 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty.js; + +import io.deephaven.plugin.Registration; +import io.deephaven.plugin.js.JsPlugin; +import io.deephaven.plugin.js.Paths; + +import java.net.URISyntaxException; +import java.nio.file.Path; + +public final class Example123Registration implements Registration { + + public Example123Registration() {} + + @Override + public void registerInto(Callback callback) { + final JsPlugin example1; + final JsPlugin example2; + final JsPlugin example3; + try { + example1 = example1(); + example2 = example2(); + example3 = example3(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + callback.register(example1); + callback.register(example2); + callback.register(example3); + } + + private static JsPlugin example1() throws URISyntaxException { + final Path resourcePath = Path.of(Sentinel.class.getResource("examples/@deephaven_test/example1").toURI()); + final Path main = resourcePath.relativize(resourcePath.resolve("dist/index.js")); + return JsPlugin.builder() + .name("@deephaven_test/example1") + .version("0.1.0") + .main(main) + .path(resourcePath) + .build(); + } + + private static JsPlugin example2() throws URISyntaxException { + final Path resourcePath = Path.of(Sentinel.class.getResource("examples/@deephaven_test/example2").toURI()); + final Path dist = resourcePath.relativize(resourcePath.resolve("dist")); + final Path main = dist.resolve("index.js"); + return JsPlugin.builder() + .name("@deephaven_test/example2") + .version("0.2.0") + .main(main) + .path(resourcePath) + .paths(Paths.ofPrefixes(dist)) + .build(); + } + + private static JsPlugin example3() throws URISyntaxException { + final Path resourcePath = Path.of(Sentinel.class.getResource("examples/@deephaven_test/example3").toURI()); + final Path main = resourcePath.relativize(resourcePath.resolve("index.js")); + return JsPlugin.builder() + .name("@deephaven_test/example3") + .version("0.3.0") + .main(main) + .path(resourcePath) + .build(); + } +} diff --git a/server/jetty/src/test/java/io/deephaven/server/jetty/js/Sentinel.java b/server/jetty/src/test/java/io/deephaven/server/jetty/js/Sentinel.java new file mode 100644 index 00000000000..6f1e7056ba8 --- /dev/null +++ b/server/jetty/src/test/java/io/deephaven/server/jetty/js/Sentinel.java @@ -0,0 +1,8 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.jetty.js; + +public class Sentinel { + // just for the class +} diff --git a/server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java b/server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java new file mode 100644 index 00000000000..500e7ebc0fb --- /dev/null +++ b/server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsManifestRegistration.java @@ -0,0 +1,35 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.plugin.js; + +import io.deephaven.plugin.Registration; +import io.deephaven.plugin.js.JsPlugin; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +public class JsPluginsManifestRegistration implements Registration { + + private final Path path; + + public JsPluginsManifestRegistration(Path path) { + this.path = Objects.requireNonNull(path); + } + + @Override + public void registerInto(Callback callback) { + final List plugins; + try { + plugins = JsPluginsFromManifest.of(path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + for (JsPlugin plugin : plugins) { + callback.register(plugin); + } + } +} diff --git a/server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java b/server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java new file mode 100644 index 00000000000..792bf9c7bba --- /dev/null +++ b/server/jetty/src/test/java/io/deephaven/server/plugin/js/JsPluginsNpmPackageRegistration.java @@ -0,0 +1,32 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.server.plugin.js; + +import io.deephaven.plugin.Registration; +import io.deephaven.plugin.js.JsPlugin; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.Objects; + +public class JsPluginsNpmPackageRegistration implements Registration { + + private final Path path; + + public JsPluginsNpmPackageRegistration(Path path) { + this.path = Objects.requireNonNull(path); + } + + @Override + public void registerInto(Callback callback) { + final JsPlugin plugin; + try { + plugin = JsPluginFromNpmPackage.of(path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + callback.register(plugin); + } +} diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index.js b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index.js new file mode 100644 index 00000000000..de46952cc24 --- /dev/null +++ b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index.js @@ -0,0 +1 @@ +// example1/dist/index.js \ No newline at end of file diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index2.js b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index2.js new file mode 100644 index 00000000000..ef31df67846 --- /dev/null +++ b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/dist/index2.js @@ -0,0 +1 @@ +// example1/dist/index2.js \ No newline at end of file diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/package.json b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/package.json new file mode 100644 index 00000000000..b3733e4fe6d --- /dev/null +++ b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example1/package.json @@ -0,0 +1 @@ +{"name":"@deephaven_test/example1","version":"0.1.0","main":"dist/index.js","files":["dist"]} \ No newline at end of file diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index.js b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index.js new file mode 100644 index 00000000000..f84594080ee --- /dev/null +++ b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index.js @@ -0,0 +1 @@ +// example2/dist/index.js \ No newline at end of file diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index2.js b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index2.js new file mode 100644 index 00000000000..536a8edceee --- /dev/null +++ b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/dist/index2.js @@ -0,0 +1 @@ +// example2/dist/index2.js \ No newline at end of file diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/package.json b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/package.json new file mode 100644 index 00000000000..64ca82a446b --- /dev/null +++ b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example2/package.json @@ -0,0 +1 @@ +{"name":"@deephaven_test/example2","version":"0.2.0","main":"dist/index.js","files":["dist"]} \ No newline at end of file diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example3/index.js b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example3/index.js new file mode 100644 index 00000000000..62c773b6ad4 --- /dev/null +++ b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/@deephaven_test/example3/index.js @@ -0,0 +1 @@ +// example3/index.js \ No newline at end of file diff --git a/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/manifest.json b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/manifest.json new file mode 100644 index 00000000000..eaa745d1b6b --- /dev/null +++ b/server/jetty/src/test/resources/io/deephaven/server/jetty/js/examples/manifest.json @@ -0,0 +1,19 @@ +{ + "plugins": [ + { + "name": "@deephaven_test/example1", + "main": "dist/index.js", + "version": "0.1.0" + }, + { + "name": "@deephaven_test/example2", + "main": "dist/index.js", + "version": "0.2.0" + }, + { + "name": "@deephaven_test/example3", + "main": "index.js", + "version": "0.3.0" + } + ] +} From 34077673c73c2ed107851a0d8649f3dafc12f226 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Fri, 6 Sep 2024 20:28:45 -0500 Subject: [PATCH 3/5] Artifacts should use jetty12 builds --- docker/server-jetty/build.gradle | 2 +- py/embedded-server/java-runtime/build.gradle | 2 +- py/embedded-server/java-runtime/gradle.properties | 5 +++++ .../java/io/deephaven/python/server/EmbeddedServer.java | 8 ++++---- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docker/server-jetty/build.gradle b/docker/server-jetty/build.gradle index ce7a6b8a209..e498b50987c 100644 --- a/docker/server-jetty/build.gradle +++ b/docker/server-jetty/build.gradle @@ -28,7 +28,7 @@ configurations { } dependencies { - serverApplicationDist project(path: ':server-jetty-app-11', configuration: 'applicationDist')//revert me + serverApplicationDist project(path: ':server-jetty-app', configuration: 'applicationDist') pythonWheel project(':py-server') } diff --git a/py/embedded-server/java-runtime/build.gradle b/py/embedded-server/java-runtime/build.gradle index 84b95a99276..1fadd12b881 100644 --- a/py/embedded-server/java-runtime/build.gradle +++ b/py/embedded-server/java-runtime/build.gradle @@ -9,7 +9,7 @@ configurations { } dependencies { - implementation project(':server-jetty-11')//revert me + implementation project(':server-jetty') implementation libs.dagger annotationProcessor libs.dagger.compiler diff --git a/py/embedded-server/java-runtime/gradle.properties b/py/embedded-server/java-runtime/gradle.properties index eeac3e65888..41626ddd8f0 100644 --- a/py/embedded-server/java-runtime/gradle.properties +++ b/py/embedded-server/java-runtime/gradle.properties @@ -1 +1,6 @@ io.deephaven.project.ProjectType=JAVA_LOCAL +compilerVersion=17 +runtimeVersion=17 +languageLevel=17 +testRuntimeVersion=17 +testLanguageLevel=17 diff --git a/py/embedded-server/java-runtime/src/main/java/io/deephaven/python/server/EmbeddedServer.java b/py/embedded-server/java-runtime/src/main/java/io/deephaven/python/server/EmbeddedServer.java index 5e5b7e779fb..3bc427f8904 100644 --- a/py/embedded-server/java-runtime/src/main/java/io/deephaven/python/server/EmbeddedServer.java +++ b/py/embedded-server/java-runtime/src/main/java/io/deephaven/python/server/EmbeddedServer.java @@ -22,10 +22,10 @@ import io.deephaven.server.console.python.PythonConsoleSessionModule; import io.deephaven.server.console.python.PythonGlobalScopeModule; import io.deephaven.server.healthcheck.HealthCheckModule; -import io.deephaven.server.jetty11.JettyConfig; -import io.deephaven.server.jetty11.JettyConfig.Builder; -import io.deephaven.server.jetty11.JettyServerComponent; -import io.deephaven.server.jetty11.JettyServerModule; +import io.deephaven.server.jetty.JettyConfig; +import io.deephaven.server.jetty.JettyConfig.Builder; +import io.deephaven.server.jetty.JettyServerComponent; +import io.deephaven.server.jetty.JettyServerModule; import io.deephaven.server.plugin.python.PythonPluginsRegistration; import io.deephaven.server.runner.DeephavenApiConfigModule; import io.deephaven.server.runner.DeephavenApiServer; From 0e48ea1f8c7b53bff2bcaf3e6fbf876f1f9417e4 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Sun, 8 Sep 2024 20:19:03 -0500 Subject: [PATCH 4/5] Remove deprecated cors filter in favor of handler --- .../server/jetty/JettyBackedGrpcServer.java | 106 +++++++++--------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java index 60ba36bbddc..8c5f1dbee2d 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java @@ -29,7 +29,6 @@ import org.eclipse.jetty.ee10.servlet.FilterHolder; import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler; -import org.eclipse.jetty.ee10.servlets.CrossOriginFilter; import org.eclipse.jetty.ee10.webapp.WebAppContext; import org.eclipse.jetty.ee10.websocket.jakarta.common.SessionTracker; import org.eclipse.jetty.ee10.websocket.jakarta.server.JakartaWebSocketServerContainer; @@ -51,6 +50,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.CrossOriginHandler; import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.util.ExceptionUtil; import org.eclipse.jetty.util.component.Graceful; @@ -68,6 +68,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -113,57 +114,6 @@ public JettyBackedGrpcServer( // Add an extra filter to redirect from / to /ide/ context.addFilter(HomeFilter.class, "/", EnumSet.noneOf(DispatcherType.class)); - // If requested, permit CORS requests - FilterHolder holder = new FilterHolder(CrossOriginFilter.class); - - // Permit all origins - holder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*"); - - // Only support POST - technically gRPC can use GET, but we don't use any of those methods - holder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "POST"); - - // Required request headers for gRPC, gRPC-web, flight, and deephaven - holder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, String.join(",", - // Required for CORS itself to work - HttpHeader.ORIGIN.asString(), - CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, - - // Required for gRPC - GrpcUtil.CONTENT_TYPE_KEY.name(), - GrpcUtil.TIMEOUT_KEY.name(), - - // Optional for gRPC - GrpcUtil.MESSAGE_ENCODING_KEY.name(), - GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY.name(), - GrpcUtil.CONTENT_ENCODING_KEY.name(), - GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY.name(), - - // Required for gRPC-web - "x-grpc-web", - // Optional for gRPC-web - "x-user-agent", - - // Required for Flight auth 1/2 - AuthConstants.TOKEN_NAME, - Auth2Constants.AUTHORIZATION_HEADER, - - // Required for DH gRPC browser bidi stream support - BrowserStreamInterceptor.TICKET_HEADER_NAME, - BrowserStreamInterceptor.SEQUENCE_HEADER_NAME, - BrowserStreamInterceptor.HALF_CLOSE_HEADER_NAME)); - - // Response headers that the browser will need to be able to decode - holder.setInitParameter(CrossOriginFilter.EXPOSED_HEADERS_PARAM, String.join(",", - Auth2Constants.AUTHORIZATION_HEADER, - GrpcUtil.CONTENT_TYPE_KEY.name(), - InternalStatus.CODE_KEY.name(), - InternalStatus.MESSAGE_KEY.name(), - // Not used (yet?), see io.grpc.protobuf.StatusProto - "grpc-status-details-bin")); - - // Add the filter on all requests - context.addFilter(holder, "/*", EnumSet.noneOf(DispatcherType.class)); - // Handle grpc-web connections, translate to vanilla grpc context.addFilter(new FilterHolder(new GrpcWebFilter()), "/*", EnumSet.noneOf(DispatcherType.class)); @@ -210,6 +160,54 @@ public T getEndpointInstance(Class endpointClass) { this.websocketsEnabled = false; } + // If requested, permit CORS requests + CrossOriginHandler corsHandler = new CrossOriginHandler(); + // Permit all origins + corsHandler.setAllowedOriginPatterns(Set.of("*")); + + // Only support POST - technically gRPC can use GET, but we don't use any of those methods + corsHandler.setAllowedMethods(Set.of("POST")); + + // Required request headers for gRPC, gRPC-web, flight, and deephaven + corsHandler.setAllowedHeaders(Set.of( + // Required for CORS itself to work + HttpHeader.ORIGIN.asString(), + HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN.asString(), + + // Required for gRPC + GrpcUtil.CONTENT_TYPE_KEY.name(), + GrpcUtil.TIMEOUT_KEY.name(), + + // Optional for gRPC + GrpcUtil.MESSAGE_ENCODING_KEY.name(), + GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY.name(), + GrpcUtil.CONTENT_ENCODING_KEY.name(), + GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY.name(), + + // Required for gRPC-web + "x-grpc-web", + // Optional for gRPC-web + "x-user-agent", + + // Required for Flight auth 1/2 + AuthConstants.TOKEN_NAME, + Auth2Constants.AUTHORIZATION_HEADER, + + // Required for DH gRPC browser bidi stream support + BrowserStreamInterceptor.TICKET_HEADER_NAME, + BrowserStreamInterceptor.SEQUENCE_HEADER_NAME, + BrowserStreamInterceptor.HALF_CLOSE_HEADER_NAME)); + + // Response headers that the browser will need to be able to decode + corsHandler.setExposedHeaders(Set.of( + Auth2Constants.AUTHORIZATION_HEADER, + GrpcUtil.CONTENT_TYPE_KEY.name(), + InternalStatus.CODE_KEY.name(), + InternalStatus.MESSAGE_KEY.name(), + // Not used (yet?), see io.grpc.protobuf.StatusProto + "grpc-status-details-bin")); + corsHandler.setHandler(context); + // Optionally wrap the webapp in a gzip handler final Handler handler; if (config.httpCompressionOrDefault()) { @@ -221,10 +219,10 @@ public T getEndpointInstance(Class endpointClass) { // the future as gRPC can technically operate over GET. gzipHandler.setIncludedMethods(HttpMethod.GET.asString()); // Otherwise, the other defaults seem reasonable. - gzipHandler.setHandler(context); + gzipHandler.setHandler(corsHandler); handler = gzipHandler; } else { - handler = context; + handler = corsHandler; } jetty.setHandler(handler); } From d45a99f76d648f30f96d4eb368d7b1d45ab11164 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Tue, 10 Sep 2024 16:16:18 -0500 Subject: [PATCH 5/5] Migrate js plugins to ResourceServlet --- .../io/deephaven/server/jetty/JettyBackedGrpcServer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java index 8c5f1dbee2d..6e90a921277 100644 --- a/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java +++ b/server/jetty/src/main/java/io/deephaven/server/jetty/JettyBackedGrpcServer.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.ee10.servlet.DefaultServlet; import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.ee10.servlet.FilterHolder; +import org.eclipse.jetty.ee10.servlet.ResourceServlet; import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler; import org.eclipse.jetty.ee10.webapp.WebAppContext; @@ -365,11 +366,11 @@ public void onClosed(Connection connection) { } private static ServletHolder servletHolder(String name, URI filesystemUri) { - final ServletHolder jsPlugins = new ServletHolder(name, DefaultServlet.class); + final ServletHolder jsPlugins = new ServletHolder(name, ResourceServlet.class); // Note, the URI needs explicitly be parseable as a directory URL ending in "!/", a requirement of the jetty // resource creation implementation, see // org.eclipse.jetty.util.resource.Resource.newResource(java.lang.String, boolean) - jsPlugins.setInitParameter("resourceBase", filesystemUri.toString()); + jsPlugins.setInitParameter("baseResource", filesystemUri.toString()); jsPlugins.setInitParameter("pathInfoOnly", "true"); jsPlugins.setInitParameter("dirAllowed", "false"); jsPlugins.setAsyncSupported(true);