diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 75acf5d..0ce4d19 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -5,6 +5,8 @@ on: - '*' jobs: build: + env: + RUNNER_TOOL_CACHE: /toolcache runs-on: hostinger steps: - name: Checkout sources @@ -19,7 +21,11 @@ jobs: - name: Execute Gradle build env: PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} - run: ./gradlew build publish + run: ./gradlew build + - name: Publish artifacts + env: + PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} + run: ./gradlew publish build-docker: name: "Build Docker images" runs-on: hostinger diff --git a/Dockerfile b/Dockerfile index b91d6be..546e46c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,11 +31,11 @@ USER luser WORKDIR /home/luser FROM base-release AS release -RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-cli/build,target=/home/luser/build cp build/libs/gbcs-cli-envelope*.jar gbcs.jar +RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-cli/build,target=/home/luser/build cp build/libs/gbcs-cli-envelope-*.jar gbcs.jar ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar"] FROM base-release AS release-memcached -RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-cli/build,target=/home/luser/build cp build/libs/gbcs-cli-envelope*.jar gbcs.jar +RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-cli/build,target=/home/luser/build cp build/libs/gbcs-cli-envelope-*.jar gbcs.jar RUN mkdir plugins WORKDIR /home/luser/plugins RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-memcached/build/distributions,target=/build/distributions tar -xf /build/distributions/gbcs-memcached*.tar diff --git a/build.gradle b/build.gradle index 4421797..b7681cb 100644 --- a/build.gradle +++ b/build.gradle @@ -10,16 +10,16 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.dsl.JvmTarget -allprojects { +allprojects { subproject -> group = 'net.woggioni' - if(project.currentTag.isPresent()) { - version = project.currentTag - } else { - version = project.gitRevision.map { gitRevision -> - "${getProperty('gbcs.version')}.${gitRevision[0..10]}" + if(project.currentTag.isPresent()) { + version = project.currentTag.map { it[0] }.get() + } else { + version = project.gitRevision.map { gitRevision -> + "${getProperty('gbcs.version')}.${gitRevision[0..10]}" + }.get() } - } repositories { maven { @@ -36,7 +36,7 @@ allprojects { pluginManager.withPlugin('java-library') { ext { - jpmsModuleName = project.group + '.' + project.name.replace('-', '.') + jpmsModuleName = subproject.group + '.' + subproject.name.replace('-', '.') } java { @@ -61,14 +61,13 @@ allprojects { options.compilerArgumentProviders << new CommandLineArgumentProvider() { @Override Iterable asArguments() { - return ['--patch-module', project.jpmsModuleName + '=' + project.sourceSets.main.output.asPath] + return ['--patch-module', subproject.jpmsModuleName + '=' + subproject.sourceSets.main.output.asPath] } } options.javaModuleVersion = version } } - pluginManager.withPlugin(catalog.plugins.kotlin.jvm.get().pluginId) { tasks.withType(KotlinCompile.class) { compilerOptions.jvmTarget = JvmTarget.JVM_21 @@ -101,17 +100,12 @@ allprojects { } } } - - tasks.withType(AbstractArchiveTask.class) { - archiveVersion = project.version - } } dependencies { implementation catalog.jwo implementation catalog.slf4j.api implementation catalog.netty.codec.http - implementation catalog.netty.codec.http2 api project('gbcs-base') api project('gbcs-api') diff --git a/conf/gbcs-memcached.xml b/conf/gbcs-memcached.xml index 3690025..37015c2 100644 --- a/conf/gbcs-memcached.xml +++ b/conf/gbcs-memcached.xml @@ -2,7 +2,7 @@ + xs:schemaLocation="urn:net.woggioni.gbcs-memcached jpms://net.woggioni.gbcs.memcached/net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd"> diff --git a/gbcs-api/build.gradle b/gbcs-api/build.gradle index 970f25d..516de05 100644 --- a/gbcs-api/build.gradle +++ b/gbcs-api/build.gradle @@ -7,10 +7,6 @@ plugins { dependencies { } -tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { - options.javaModuleVersion = version -} - publishing { publications { maven(MavenPublication) { diff --git a/gbcs-api/src/main/java/net/woggioni/gbcs/api/Configuration.java b/gbcs-api/src/main/java/net/woggioni/gbcs/api/Configuration.java index dc78200..982e726 100644 --- a/gbcs-api/src/main/java/net/woggioni/gbcs/api/Configuration.java +++ b/gbcs-api/src/main/java/net/woggioni/gbcs/api/Configuration.java @@ -1,6 +1,7 @@ package net.woggioni.gbcs.api; +import lombok.EqualsAndHashCode; import lombok.Value; import java.nio.file.Path; @@ -23,25 +24,18 @@ public class Configuration { @Value public static class Group { + @EqualsAndHashCode.Include String name; Set roles; - - @Override - public int hashCode() { - return name.hashCode(); - } } @Value public static class User { + @EqualsAndHashCode.Include String name; String password; Set groups; - @Override - public int hashCode() { - return name.hashCode(); - } public Set getRoles() { return groups.stream() diff --git a/gbcs-base/build.gradle b/gbcs-base/build.gradle index 917f3d4..073a216 100644 --- a/gbcs-base/build.gradle +++ b/gbcs-base/build.gradle @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id 'java-library' @@ -10,16 +8,6 @@ plugins { dependencies { compileOnly project(':gbcs-api') compileOnly catalog.slf4j.api - api catalog.jwo -} - -tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { - options.compilerArgs << '--patch-module' << 'net.woggioni.gbcs.base=' + project.sourceSets.main.output.asPath - options.javaModuleVersion = version -} - -tasks.named("compileKotlin", KotlinCompile.class) { - compilerOptions.jvmTarget = JvmTarget.JVM_21 } publishing { diff --git a/gbcs-base/src/main/java/module-info.java b/gbcs-base/src/main/java/module-info.java index 7ebf117..2fa3505 100644 --- a/gbcs-base/src/main/java/module-info.java +++ b/gbcs-base/src/main/java/module-info.java @@ -3,7 +3,6 @@ module net.woggioni.gbcs.base { requires java.logging; requires org.slf4j; requires kotlin.stdlib; - requires net.woggioni.jwo; exports net.woggioni.gbcs.base; } \ No newline at end of file diff --git a/gbcs-base/src/main/kotlin/net/woggioni/gbcs/base/ClasspathUrlStreamHandlerFactoryProvider.kt b/gbcs-base/src/main/kotlin/net/woggioni/gbcs/base/GbcsUrlStreamHandlerFactory.kt similarity index 59% rename from gbcs-base/src/main/kotlin/net/woggioni/gbcs/base/ClasspathUrlStreamHandlerFactoryProvider.kt rename to gbcs-base/src/main/kotlin/net/woggioni/gbcs/base/GbcsUrlStreamHandlerFactory.kt index 826ac67..8f95786 100644 --- a/gbcs-base/src/main/kotlin/net/woggioni/gbcs/base/ClasspathUrlStreamHandlerFactoryProvider.kt +++ b/gbcs-base/src/main/kotlin/net/woggioni/gbcs/base/GbcsUrlStreamHandlerFactory.kt @@ -1,16 +1,20 @@ package net.woggioni.gbcs.base +import java.io.IOException import java.io.InputStream import java.net.URL import java.net.URLConnection import java.net.URLStreamHandler import java.net.URLStreamHandlerFactory +import java.util.Optional import java.util.concurrent.atomic.AtomicBoolean import java.util.stream.Collectors -class ClasspathUrlStreamHandlerFactoryProvider : URLStreamHandlerFactory { - private class Handler(private val classLoader: ClassLoader = ClasspathUrlStreamHandlerFactoryProvider::class.java.classLoader) : URLStreamHandler() { +class GbcsUrlStreamHandlerFactory : URLStreamHandlerFactory { + + private class ClasspathHandler(private val classLoader: ClassLoader = GbcsUrlStreamHandlerFactory::class.java.classLoader) : + URLStreamHandler() { override fun openConnection(u: URL): URLConnection? { return javaClass.module @@ -20,23 +24,48 @@ class ClasspathUrlStreamHandlerFactoryProvider : URLStreamHandlerFactory { val i = path.lastIndexOf('/') val packageName = path.substring(0, i).replace('/', '.') val modules = packageMap[packageName]!! - ModuleResourceURLConnection( + ClasspathResourceURLConnection( u, modules ) } ?: classLoader.getResource(u.path)?.let(URL::openConnection) - } } + } + + private class JpmsHandler : URLStreamHandler() { + + override fun openConnection(u: URL): URLConnection { + val thisModule = javaClass.module + val sourceModule = Optional.ofNullable(thisModule) + .map { obj: Module -> obj.layer } + .flatMap { layer: ModuleLayer -> + val moduleName = u.host + layer.findModule(moduleName) + }.orElse(thisModule) + return JpmsResourceURLConnection(u, sourceModule) + } + } + + private class JpmsResourceURLConnection(url: URL, private val module: Module) : URLConnection(url) { + override fun connect() { + } + + @Throws(IOException::class) + override fun getInputStream(): InputStream { + return module.getResourceAsStream(getURL().path) + } + } override fun createURLStreamHandler(protocol: String): URLStreamHandler? { return when (protocol) { - "classpath" -> Handler() + "classpath" -> ClasspathHandler() + "jpms" -> JpmsHandler() else -> null } } - private class ModuleResourceURLConnection(url: URL?, private val modules: List) : + private class ClasspathResourceURLConnection(url: URL?, private val modules: List) : URLConnection(url) { override fun connect() {} @@ -53,12 +82,12 @@ class ClasspathUrlStreamHandlerFactoryProvider : URLStreamHandlerFactory { private val installed = AtomicBoolean(false) fun install() { if (!installed.getAndSet(true)) { - URL.setURLStreamHandlerFactory(ClasspathUrlStreamHandlerFactoryProvider()) + URL.setURLStreamHandlerFactory(GbcsUrlStreamHandlerFactory()) } } private val packageMap: Map> by lazy { - ClasspathUrlStreamHandlerFactoryProvider::class.java.module.layer + GbcsUrlStreamHandlerFactory::class.java.module.layer .modules() .stream() .flatMap { m: Module -> diff --git a/gbcs-cli/build.gradle b/gbcs-cli/build.gradle index a5c4794..58cda27 100644 --- a/gbcs-cli/build.gradle +++ b/gbcs-cli/build.gradle @@ -18,8 +18,6 @@ Property mainClassName = objects.property(String.class) mainClassName.set('net.woggioni.gbcs.cli.GradleBuildCacheServerCli') tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { - options.compilerArgs << '--patch-module' << 'net.woggioni.gbcs.cli=' + project.sourceSets.main.output.asPath - options.javaModuleVersion = version options.javaModuleMainClass = mainClassName } @@ -36,8 +34,6 @@ dependencies { implementation catalog.netty.codec.http implementation catalog.picocli -// implementation project(':gbcs-base') -// implementation project(':gbcs-api') implementation rootProject // runtimeOnly catalog.slf4j.jdk14 @@ -50,11 +46,6 @@ Provider envelopeJarTaskProvider = tasks.named('envelopeJar', E systemProperties['logback.configurationFile'] = 'classpath:net/woggioni/gbcs/cli/logback.xml' } -def envelopeJarArtifact = artifacts.add('archives', envelopeJarTaskProvider.get().archiveFile.get().asFile) { - type = 'jar' - builtBy envelopeJarTaskProvider -} - tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) { mainClass = 'net.woggioni.gbcs.GraalNativeImageConfiguration' } @@ -68,7 +59,7 @@ tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) { publishing { publications { maven(MavenPublication) { - artifact envelopeJarArtifact + artifact envelopeJar } } } diff --git a/gbcs-cli/src/main/kotlin/net/woggioni/gbcs/cli/GradleBuildCacheServerCli.kt b/gbcs-cli/src/main/kotlin/net/woggioni/gbcs/cli/GradleBuildCacheServerCli.kt index 5bf5ece..753bf1c 100644 --- a/gbcs-cli/src/main/kotlin/net/woggioni/gbcs/cli/GradleBuildCacheServerCli.kt +++ b/gbcs-cli/src/main/kotlin/net/woggioni/gbcs/cli/GradleBuildCacheServerCli.kt @@ -2,7 +2,7 @@ package net.woggioni.gbcs.cli import net.woggioni.gbcs.GradleBuildCacheServer import net.woggioni.gbcs.GradleBuildCacheServer.Companion.DEFAULT_CONFIGURATION_URL -import net.woggioni.gbcs.base.ClasspathUrlStreamHandlerFactoryProvider +import net.woggioni.gbcs.base.GbcsUrlStreamHandlerFactory import net.woggioni.gbcs.base.contextLogger import net.woggioni.gbcs.base.debug import net.woggioni.gbcs.base.info @@ -29,7 +29,7 @@ class GradleBuildCacheServerCli(application : Application, private val log : Log @JvmStatic fun main(vararg args: String) { Thread.currentThread().contextClassLoader = GradleBuildCacheServerCli::class.java.classLoader - ClasspathUrlStreamHandlerFactoryProvider.install() + GbcsUrlStreamHandlerFactory.install() val log = contextLogger() val app = Application.builder("gbcs") .configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR") diff --git a/gbcs-memcached/build.gradle b/gbcs-memcached/build.gradle index 2d50461..abd2869 100644 --- a/gbcs-memcached/build.gradle +++ b/gbcs-memcached/build.gradle @@ -30,15 +30,6 @@ dependencies { implementation catalog.xmemcached } -tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { - options.compilerArgs << '--patch-module' << 'net.woggioni.gbcs.memcached=' + project.sourceSets.main.output.asPath - options.javaModuleVersion = version -} - -tasks.named("compileKotlin", KotlinCompile.class) { - compilerOptions.jvmTarget = JvmTarget.JVM_21 -} - Provider bundleTask = tasks.register("bundle", Tar) { from(tasks.named(JavaPlugin.JAR_TASK_NAME)) from(configurations.bundle) @@ -49,16 +40,10 @@ tasks.named(BasePlugin.ASSEMBLE_TASK_NAME) { dependsOn(bundleTask) } -def bundleArtifact = artifacts.add('archives', bundleTask.get().archiveFile.get().asFile) { - type = 'tar' - builtBy bundleTask -} - - publishing { publications { maven(MavenPublication) { - artifact bundleArtifact + artifact bundleTask } } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 9c1ad5d..f526aa9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.configuration-cache=false org.gradle.parallel=true -org.gradle.caching=false +org.gradle.caching=true gbcs.version = 0.0.1 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 3c1d879..5dca7ce 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,5 +1,4 @@ import net.woggioni.gbcs.api.CacheProvider; -import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider; import net.woggioni.gbcs.cache.FileSystemCacheProvider; module net.woggioni.gbcs { @@ -11,7 +10,6 @@ module net.woggioni.gbcs { requires io.netty.buffer; requires io.netty.transport; requires io.netty.codec.http; - requires io.netty.codec.http2; requires io.netty.common; requires io.netty.handler; requires io.netty.codec; @@ -21,11 +19,10 @@ module net.woggioni.gbcs { requires net.woggioni.gbcs.api; exports net.woggioni.gbcs; + opens net.woggioni.gbcs; opens net.woggioni.gbcs.schema; - uses java.net.URLStreamHandlerFactory; - provides java.net.URLStreamHandlerFactory with ClasspathUrlStreamHandlerFactoryProvider; uses CacheProvider; provides CacheProvider with FileSystemCacheProvider; } \ No newline at end of file diff --git a/src/main/java/net/woggioni/gbcs/url/ClasspathUrlStreamHandlerFactoryProvider.java b/src/main/java/net/woggioni/gbcs/url/ClasspathUrlStreamHandlerFactoryProvider.java deleted file mode 100644 index 8b15bbe..0000000 --- a/src/main/java/net/woggioni/gbcs/url/ClasspathUrlStreamHandlerFactoryProvider.java +++ /dev/null @@ -1,104 +0,0 @@ -package net.woggioni.gbcs.url; - -import net.woggioni.jwo.Fun; -import net.woggioni.jwo.LazyValue; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLStreamHandler; -import java.net.URLStreamHandlerFactory; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -public class ClasspathUrlStreamHandlerFactoryProvider implements URLStreamHandlerFactory { - - private static final AtomicBoolean installed = new AtomicBoolean(false); - public static void install() { - if(!installed.getAndSet(true)) { - URL.setURLStreamHandlerFactory(new ClasspathUrlStreamHandlerFactoryProvider()); - } - } - - private static final LazyValue>> packageMap = LazyValue.of(() -> - ClasspathUrlStreamHandlerFactoryProvider.class.getModule().getLayer() - .modules() - .stream() - .flatMap(m -> m.getPackages().stream().map(p -> Map.entry(p, m))) - .collect( - Collectors.groupingBy( - Map.Entry::getKey, - Collectors.mapping( - Map.Entry::getValue, - Collectors.toUnmodifiableList() - ) - ) - ), - LazyValue.ThreadSafetyMode.NONE - ); - - - private static class Handler extends URLStreamHandler { - private final ClassLoader classLoader; - - public Handler() { - this.classLoader = getClass().getClassLoader(); - } - - public Handler(ClassLoader classLoader) { - this.classLoader = classLoader; - } - - @Override - protected URLConnection openConnection(URL u) throws IOException { - return Optional.ofNullable(getClass().getModule()) - .filter(m -> m.getLayer() != null) - .map(m -> { - final var path = u.getPath(); - final var i = path.lastIndexOf('/'); - final var packageName = path.substring(0, i).replace('/', '.'); - final var modules = packageMap.get().get(packageName); - return (URLConnection) new ModuleResourceURLConnection(u, modules); - }) - .or(() -> Optional.of(classLoader).map(cl -> cl.getResource(u.getPath())).map((Fun) URL::openConnection)) - .orElse(null); - } - } - - @Override - public URLStreamHandler createURLStreamHandler(String protocol) { - URLStreamHandler result; - switch (protocol) { - case "classpath": - result = new Handler(); - break; - default: - result = null; - } - return result; - } - - private static final class ModuleResourceURLConnection extends URLConnection { - private final List modules; - - ModuleResourceURLConnection(URL url, List modules) { - super(url); - this.modules = modules; - } - - public void connect() { - } - - public InputStream getInputStream() throws IOException { - for(final var module : modules) { - final var result = module.getResourceAsStream(getURL().getPath()); - if(result != null) return result; - } - return null; - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/net/woggioni/gbcs/GradleBuildCacheServer.kt b/src/main/kotlin/net/woggioni/gbcs/GradleBuildCacheServer.kt index 1711819..07ab186 100644 --- a/src/main/kotlin/net/woggioni/gbcs/GradleBuildCacheServer.kt +++ b/src/main/kotlin/net/woggioni/gbcs/GradleBuildCacheServer.kt @@ -13,6 +13,7 @@ import io.netty.channel.ChannelOption import io.netty.channel.ChannelPromise import io.netty.channel.DefaultFileRegion import io.netty.channel.EventLoopGroup +import io.netty.channel.MultithreadEventLoopGroup import io.netty.channel.SimpleChannelInboundHandler import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.socket.nio.NioServerSocketChannel @@ -34,7 +35,6 @@ import io.netty.handler.codec.http.HttpServerCodec import io.netty.handler.codec.http.HttpUtil import io.netty.handler.codec.http.HttpVersion import io.netty.handler.codec.http.LastHttpContent -import io.netty.handler.codec.http2.Http2FrameCodecBuilder import io.netty.handler.ssl.ApplicationProtocolNames import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler import io.netty.handler.ssl.ClientAuth @@ -58,19 +58,12 @@ import net.woggioni.gbcs.base.PasswordSecurity.decodePasswordHash import net.woggioni.gbcs.base.PasswordSecurity.hashPassword import net.woggioni.gbcs.base.Xml import net.woggioni.gbcs.base.contextLogger -import net.woggioni.gbcs.base.debug -import net.woggioni.gbcs.base.info import net.woggioni.gbcs.configuration.Parser import net.woggioni.gbcs.configuration.Serializer -import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider -import net.woggioni.jwo.Application import net.woggioni.jwo.JWO import net.woggioni.jwo.Tuple2 -import java.io.ByteArrayOutputStream import java.io.OutputStream import java.net.InetSocketAddress -import java.net.URL -import java.net.URLStreamHandlerFactory import java.nio.channels.FileChannel import java.nio.file.Files import java.nio.file.Path @@ -223,22 +216,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) { companion object { - private fun getServerAPNHandler(): ApplicationProtocolNegotiationHandler { - val serverAPNHandler: ApplicationProtocolNegotiationHandler = - object : ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_2) { - override fun configurePipeline(ctx: ChannelHandlerContext, protocol: String) { - if (ApplicationProtocolNames.HTTP_2 == protocol) { - ctx.pipeline().addLast( - Http2FrameCodecBuilder.forServer().build() - ) - return - } - throw IllegalStateException("Protocol: $protocol not supported") - } - } - return serverAPNHandler - } - fun loadKeystore(file: Path, password: String?): KeyStore { val ext = JWO.splitExtension(file) .map(Tuple2::get_2) @@ -299,6 +276,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) { } if (sslContext != null) { val sslHandler = sslContext.newHandler(ch.alloc()) + pipeline.addLast(sslHandler) if (auth is Configuration.ClientCertificateAuthentication) { val roleAuthorizer = RoleAuthorizer() @@ -310,7 +288,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) { ) } } -// pipeline.addLast(getServerAPNHandler()) pipeline.addLast(HttpServerCodec()) pipeline.addLast(HttpChunkContentCompressor(1024)) pipeline.addLast(ChunkedWriteHandler()) @@ -370,20 +347,12 @@ class GradleBuildCacheServer(private val cfg: Configuration) { companion object { private val log = contextLogger() - - private fun splitPath(req: HttpRequest): Pair { - val uri = req.uri() - val i = uri.lastIndexOf('/') - if (i < 0) throw RuntimeException(String.format("Malformed request URI: '%s'", uri)) - return uri.substring(0, i) to uri.substring(i + 1) - } } override fun channelRead0(ctx: ChannelHandlerContext, msg: FullHttpRequest) { val keepAlive: Boolean = HttpUtil.isKeepAlive(msg) val method = msg.method() if (method === HttpMethod.GET) { -// val (prefix, key) = splitPath(msg) val path = Path.of(msg.uri()) val prefix = path.parent val key = path.fileName.toString() @@ -492,26 +461,27 @@ class GradleBuildCacheServer(private val cfg: Configuration) { override fun close() { try { - closeFuture.sync() - } finally { val fut1 = workerGroup.shutdownGracefully() val fut2 = if (bossGroup !== workerGroup) { bossGroup.shutdownGracefully() } else null fut1.sync() fut2?.sync() + } finally { + closeFuture.sync() } } } fun run(): ServerHandle { // Create the multithreaded event loops for the server - val bossGroup = NioEventLoopGroup() + val parallelism = Runtime.getRuntime().availableProcessors() * 2 + val bossGroup = NioEventLoopGroup(parallelism, Executors.defaultThreadFactory()) val serverSocketChannel = NioServerSocketChannel::class.java val workerGroup = if (cfg.isUseVirtualThread) { NioEventLoopGroup(0, Executors.newVirtualThreadPerTaskExecutor()) } else { - NioEventLoopGroup(0, Executors.newWorkStealingPool()) + NioEventLoopGroup(parallelism, Executors.defaultThreadFactory()) } // A helper class that simplifies server configuration val bootstrap = ServerBootstrap().apply { @@ -532,37 +502,8 @@ class GradleBuildCacheServer(private val cfg: Configuration) { companion object { - private val log by lazy { - contextLogger() - } - - private const val PROTOCOL_HANDLER = "java.protocol.handler.pkgs" - private const val HANDLERS_PACKAGE = "net.woggioni.gbcs.url" - val DEFAULT_CONFIGURATION_URL by lazy { "classpath:net/woggioni/gbcs/gbcs-default.xml".toUrl() } - /** - * Reset any cached handlers just in case a jar protocol has already been used. We - * reset the handler by trying to set a null [URLStreamHandlerFactory] which - * should have no effect other than clearing the handlers cache. - */ - private fun resetCachedUrlHandlers() { - try { - URL.setURLStreamHandlerFactory(null) - } catch (ex: Error) { - // Ignore - } - } - - fun registerUrlProtocolHandler() { - val handlers = System.getProperty(PROTOCOL_HANDLER, "") - System.setProperty( - PROTOCOL_HANDLER, - if (handlers == null || handlers.isEmpty()) HANDLERS_PACKAGE else "$handlers|$HANDLERS_PACKAGE" - ) - resetCachedUrlHandlers() - } - fun loadConfiguration(configurationFile: Path): Configuration { val dbf = Xml.newDocumentBuilderFactory(null) val db = dbf.newDocumentBuilder() @@ -573,61 +514,5 @@ class GradleBuildCacheServer(private val cfg: Configuration) { fun dumpConfiguration(conf : Configuration, outputStream: OutputStream) { Xml.write(Serializer.serialize(conf), outputStream) } - - fun loadConfiguration(args: Array): Configuration { -// Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader - val app = Application.builder("gbcs") - .configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR") - .configurationDirectoryPropertyKey("net.woggioni.gbcs.conf.dir") - .build() - val confDir = app.computeConfigurationDirectory() - val configurationFile = confDir.resolve("gbcs.xml") - if (!Files.exists(configurationFile)) { - log.info { - "Creating default configuration file at '$configurationFile'" - } - Files.createDirectories(confDir) - val defaultConfigurationFileResource = DEFAULT_CONFIGURATION_URL - Files.newOutputStream(configurationFile).use { outputStream -> - defaultConfigurationFileResource.openStream().use { inputStream -> - JWO.copy(inputStream, outputStream) - } - } - } -// val schemaUrl = javaClass.getResource("/net/woggioni/gbcs/gbcs.xsd") -// val schemaUrl = GBCS.CONFIGURATION_SCHEMA_URL - val dbf = Xml.newDocumentBuilderFactory(null) -// dbf.schema = Xml.getSchema(this::class.java.module.getResourceAsStream("/net/woggioni/gbcs/gbcs.xsd")) -// dbf.schema = Xml.getSchema(schemaUrl) - val db = dbf.newDocumentBuilder() - val doc = Files.newInputStream(configurationFile).use(db::parse) - return Parser.parse(doc) - } - - @JvmStatic - fun main(args: Array) { - ClasspathUrlStreamHandlerFactoryProvider.install() - val configuration = loadConfiguration(args) - log.debug { - ByteArrayOutputStream().also { - Xml.write(Serializer.serialize(configuration), it) - }.let { - "Server configuration:\n${String(it.toByteArray())}" - } - } - GradleBuildCacheServer(configuration).run().use { - } - } } } - -object GraalNativeImageConfiguration { - @JvmStatic - fun main(args: Array) { - val conf = GradleBuildCacheServer.loadConfiguration(args) - GradleBuildCacheServer(conf).run().use { - Thread.sleep(3000) - it.shutdown() - } - } -} \ No newline at end of file diff --git a/src/main/resources/net/woggioni/gbcs/gbcs-default.xml b/src/main/resources/net/woggioni/gbcs/gbcs-default.xml index 38006f9..be24a62 100644 --- a/src/main/resources/net/woggioni/gbcs/gbcs-default.xml +++ b/src/main/resources/net/woggioni/gbcs/gbcs-default.xml @@ -1,7 +1,7 @@ + xs:schemaLocation="urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd"> diff --git a/src/test/java/net/woggioni/gbcs/utils/NetworkUtils.java b/src/test/java/net/woggioni/gbcs/utils/NetworkUtils.java new file mode 100644 index 0000000..7f483c7 --- /dev/null +++ b/src/test/java/net/woggioni/gbcs/utils/NetworkUtils.java @@ -0,0 +1,30 @@ +package net.woggioni.gbcs.utils; + +import net.woggioni.jwo.JWO; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; + +public class NetworkUtils { + + private static final int MAX_ATTEMPTS = 50; + + public static int getFreePort() { + int count = 0; + while(count < MAX_ATTEMPTS) { + try (ServerSocket serverSocket = new ServerSocket(0, 50, InetAddress.getLocalHost())) { + final var candidate = serverSocket.getLocalPort(); + if (candidate > 0) { + return candidate; + } else { + JWO.newThrowable(RuntimeException.class, "Got invalid port number: %d", candidate); + throw new RuntimeException("Error trying to find an open port"); + } + } catch (IOException ignored) { + ++count; + } + } + throw new RuntimeException("Error trying to find an open port"); + } +} diff --git a/src/test/kotlin/net/woggioni/gbcs/test/BasicAuthServerTest.kt b/src/test/kotlin/net/woggioni/gbcs/test/BasicAuthServerTest.kt index 155452b..ae559ee 100644 --- a/src/test/kotlin/net/woggioni/gbcs/test/BasicAuthServerTest.kt +++ b/src/test/kotlin/net/woggioni/gbcs/test/BasicAuthServerTest.kt @@ -1,15 +1,17 @@ package net.woggioni.gbcs.test import io.netty.handler.codec.http.HttpResponseStatus -import net.woggioni.gbcs.auth.AbstractNettyHttpAuthenticator.Companion.hashPassword -import net.woggioni.gbcs.api.Role -import net.woggioni.gbcs.base.Xml import net.woggioni.gbcs.api.Configuration +import net.woggioni.gbcs.api.Role +import net.woggioni.gbcs.base.PasswordSecurity.hashPassword +import net.woggioni.gbcs.base.Xml import net.woggioni.gbcs.cache.FileSystemCacheConfiguration import net.woggioni.gbcs.configuration.Serializer +import net.woggioni.gbcs.utils.NetworkUtils import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test +import java.io.IOException import java.net.ServerSocket import java.net.URI import java.net.http.HttpClient @@ -41,7 +43,7 @@ class BasicAuthServerTest : AbstractServerTest() { val writersGroup = Configuration.Group("writers", setOf(Role.Writer)) cfg = Configuration( "127.0.0.1", - ServerSocket(0).localPort + 1, + NetworkUtils.getFreePort(), serverPath, listOf( Configuration.User("user1", hashPassword(PASSWORD), setOf(readersGroup)), diff --git a/src/test/kotlin/net/woggioni/gbcs/test/ConfigurationTest.kt b/src/test/kotlin/net/woggioni/gbcs/test/ConfigurationTest.kt index 8dd6f76..0e41bb2 100644 --- a/src/test/kotlin/net/woggioni/gbcs/test/ConfigurationTest.kt +++ b/src/test/kotlin/net/woggioni/gbcs/test/ConfigurationTest.kt @@ -1,10 +1,10 @@ package net.woggioni.gbcs.test +import net.woggioni.gbcs.base.GbcsUrlStreamHandlerFactory import net.woggioni.gbcs.base.GBCS.toUrl import net.woggioni.gbcs.base.Xml import net.woggioni.gbcs.configuration.Parser import net.woggioni.gbcs.configuration.Serializer -import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest @@ -22,7 +22,7 @@ class ConfigurationTest { ) @ParameterizedTest fun test(configurationUrl: String, @TempDir testDir: Path) { - ClasspathUrlStreamHandlerFactoryProvider.install() + GbcsUrlStreamHandlerFactory.install() val doc = Xml.parseXml(configurationUrl.toUrl()) val cfg = Parser.parse(doc) val configFile = testDir.resolve("gbcs.xml") diff --git a/src/test/kotlin/net/woggioni/gbcs/test/NoAuthServerTest.kt b/src/test/kotlin/net/woggioni/gbcs/test/NoAuthServerTest.kt index b02586a..d7e6bee 100644 --- a/src/test/kotlin/net/woggioni/gbcs/test/NoAuthServerTest.kt +++ b/src/test/kotlin/net/woggioni/gbcs/test/NoAuthServerTest.kt @@ -5,6 +5,7 @@ import net.woggioni.gbcs.base.Xml import net.woggioni.gbcs.api.Configuration import net.woggioni.gbcs.cache.FileSystemCacheConfiguration import net.woggioni.gbcs.configuration.Serializer +import net.woggioni.gbcs.utils.NetworkUtils import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test @@ -32,7 +33,7 @@ class NoAuthServerTest : AbstractServerTest() { this.cacheDir = testDir.resolve("cache") cfg = Configuration( "127.0.0.1", - ServerSocket(0).localPort + 1, + NetworkUtils.getFreePort(), serverPath, emptyMap(), emptyMap(), diff --git a/src/test/kotlin/net/woggioni/gbcs/test/TlsServerTest.kt b/src/test/kotlin/net/woggioni/gbcs/test/TlsServerTest.kt index d2de5b3..90fcf4a 100644 --- a/src/test/kotlin/net/woggioni/gbcs/test/TlsServerTest.kt +++ b/src/test/kotlin/net/woggioni/gbcs/test/TlsServerTest.kt @@ -8,6 +8,7 @@ import net.woggioni.gbcs.cache.FileSystemCacheConfiguration import net.woggioni.gbcs.configuration.Serializer import net.woggioni.gbcs.utils.CertificateUtils import net.woggioni.gbcs.utils.CertificateUtils.X509Credentials +import net.woggioni.gbcs.utils.NetworkUtils import org.bouncycastle.asn1.x500.X500Name import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Order @@ -146,7 +147,7 @@ class TlsServerTest : AbstractServerTest() { createKeyStoreAndTrustStore() cfg = Configuration( "127.0.0.1", - ServerSocket(0).localPort + 1, + NetworkUtils.getFreePort(), serverPath, users.asSequence().map { it.name to it }.toMap(), sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(), @@ -227,7 +228,6 @@ class TlsServerTest : AbstractServerTest() { @Test @Order(3) fun getAsAWriterUser() { - val (key, _) = keyValuePair val user = cfg.users.values.find { Role.Writer in it.roles @@ -245,7 +245,6 @@ class TlsServerTest : AbstractServerTest() { @Test @Order(4) fun putAsAWriterUser() { - val (key, value) = keyValuePair val user = cfg.users.values.find { Role.Writer in it.roles diff --git a/src/test/resources/net/woggioni/gbcs/test/gbcs-default.xml b/src/test/resources/net/woggioni/gbcs/test/gbcs-default.xml index 369b9e3..0a85a8f 100644 --- a/src/test/resources/net/woggioni/gbcs/test/gbcs-default.xml +++ b/src/test/resources/net/woggioni/gbcs/test/gbcs-default.xml @@ -1,7 +1,7 @@ + xs:schemaLocation="urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd"> diff --git a/src/test/resources/net/woggioni/gbcs/test/gbcs-memcached.xml b/src/test/resources/net/woggioni/gbcs/test/gbcs-memcached.xml index 1533137..918a44d 100644 --- a/src/test/resources/net/woggioni/gbcs/test/gbcs-memcached.xml +++ b/src/test/resources/net/woggioni/gbcs/test/gbcs-memcached.xml @@ -2,7 +2,7 @@ + xs:schemaLocation="urn:net.woggioni.gbcs-memcached jpms://net.woggioni.gbcs.memcached/net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd">