Compare commits

...

2 Commits

Author SHA1 Message Date
d2c00402df updated Netty version 2025-01-10 22:02:11 +08:00
d701157b06 added jpms url protocol
Some checks failed
CI / build (push) Successful in 31s
CI / Build Docker images (push) Failing after 15s
2025-01-10 17:09:40 +08:00
27 changed files with 220 additions and 383 deletions

View File

@@ -5,6 +5,8 @@ on:
- '*' - '*'
jobs: jobs:
build: build:
env:
RUNNER_TOOL_CACHE: /toolcache
runs-on: hostinger runs-on: hostinger
steps: steps:
- name: Checkout sources - name: Checkout sources
@@ -19,7 +21,11 @@ jobs:
- name: Execute Gradle build - name: Execute Gradle build
env: env:
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} 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: build-docker:
name: "Build Docker images" name: "Build Docker images"
runs-on: hostinger runs-on: hostinger

View File

@@ -9,7 +9,7 @@ WORKDIR /home/ubuntu
RUN mkdir gbcs RUN mkdir gbcs
WORKDIR /home/ubuntu/gbcs WORKDIR /home/ubuntu/gbcs
COPY --chown=ubuntu:users .git .git COPY --chown=ubuntu:users ./.git ./.git
COPY --chown=ubuntu:users gbcs-base gbcs-base COPY --chown=ubuntu:users gbcs-base gbcs-base
COPY --chown=ubuntu:users gbcs-api gbcs-api COPY --chown=ubuntu:users gbcs-api gbcs-api
COPY --chown=ubuntu:users gbcs-memcached gbcs-memcached COPY --chown=ubuntu:users gbcs-memcached gbcs-memcached
@@ -21,7 +21,7 @@ COPY --chown=ubuntu:users gradle.properties gradle.properties
COPY --chown=ubuntu:users gradle gradle COPY --chown=ubuntu:users gradle gradle
COPY --chown=ubuntu:users gradlew gradlew COPY --chown=ubuntu:users gradlew gradlew
RUN --mount=type=cache,target=/home/ubuntu/.gradle,uid=1000,gid=1000 ./gradlew --no-daemon assemble RUN --mount=type=cache,target=/home/ubuntu/.gradle,uid=1000,gid=1000 ./gradlew --no-daemon clean assemble
FROM alpine:latest AS base-release FROM alpine:latest AS base-release
RUN --mount=type=cache,target=/var/cache/apk apk update RUN --mount=type=cache,target=/var/cache/apk apk update
@@ -31,11 +31,11 @@ USER luser
WORKDIR /home/luser WORKDIR /home/luser
FROM base-release AS release 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"] ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar"]
FROM base-release AS release-memcached 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 RUN mkdir plugins
WORKDIR /home/luser/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 RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-memcached/build/distributions,target=/build/distributions tar -xf /build/distributions/gbcs-memcached*.tar

View File

@@ -10,16 +10,16 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
allprojects { allprojects { subproject ->
group = 'net.woggioni' group = 'net.woggioni'
if(project.currentTag.isPresent()) { if(project.currentTag.isPresent()) {
version = project.currentTag version = project.currentTag.map { it[0] }.get()
} else { } else {
version = project.gitRevision.map { gitRevision -> version = project.gitRevision.map { gitRevision ->
"${getProperty('gbcs.version')}.${gitRevision[0..10]}" "${getProperty('gbcs.version')}.${gitRevision[0..10]}"
}.get()
} }
}
repositories { repositories {
maven { maven {
@@ -36,7 +36,7 @@ allprojects {
pluginManager.withPlugin('java-library') { pluginManager.withPlugin('java-library') {
ext { ext {
jpmsModuleName = project.group + '.' + project.name.replace('-', '.') jpmsModuleName = subproject.group + '.' + subproject.name.replace('-', '.')
} }
java { java {
@@ -61,14 +61,13 @@ allprojects {
options.compilerArgumentProviders << new CommandLineArgumentProvider() { options.compilerArgumentProviders << new CommandLineArgumentProvider() {
@Override @Override
Iterable<String> asArguments() { Iterable<String> asArguments() {
return ['--patch-module', project.jpmsModuleName + '=' + project.sourceSets.main.output.asPath] return ['--patch-module', subproject.jpmsModuleName + '=' + subproject.sourceSets.main.output.asPath]
} }
} }
options.javaModuleVersion = version options.javaModuleVersion = version
} }
} }
pluginManager.withPlugin(catalog.plugins.kotlin.jvm.get().pluginId) { pluginManager.withPlugin(catalog.plugins.kotlin.jvm.get().pluginId) {
tasks.withType(KotlinCompile.class) { tasks.withType(KotlinCompile.class) {
compilerOptions.jvmTarget = JvmTarget.JVM_21 compilerOptions.jvmTarget = JvmTarget.JVM_21
@@ -101,17 +100,12 @@ allprojects {
} }
} }
} }
tasks.withType(AbstractArchiveTask.class) {
archiveVersion = project.version
}
} }
dependencies { dependencies {
implementation catalog.jwo implementation catalog.jwo
implementation catalog.slf4j.api implementation catalog.slf4j.api
implementation catalog.netty.codec.http implementation catalog.netty.codec.http
implementation catalog.netty.codec.http2
api project('gbcs-base') api project('gbcs-base')
api project('gbcs-api') api project('gbcs-api')

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" <gbcs:server useVirtualThreads="true" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gbcs="urn:net.woggioni.gbcs" xmlns:gbcs="urn:net.woggioni.gbcs"
xmlns:gbcs-memcached="urn:net.woggioni.gbcs-memcached" xmlns:gbcs-memcached="urn:net.woggioni.gbcs-memcached"
xs:schemaLocation="urn:net.woggioni.gbcs-memcached classpath:net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs classpath:net/woggioni/gbcs/schema/gbcs.xsd"> 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">
<bind host="0.0.0.0" port="13080" /> <bind host="0.0.0.0" port="13080" />
<cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="16777216" compression-mode="zip"> <cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="16777216" compression-mode="zip">
<server host="memcached" port="11211"/> <server host="memcached" port="11211"/>

View File

@@ -7,10 +7,6 @@ plugins {
dependencies { dependencies {
} }
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
options.javaModuleVersion = version
}
publishing { publishing {
publications { publications {
maven(MavenPublication) { maven(MavenPublication) {

View File

@@ -1,6 +1,7 @@
package net.woggioni.gbcs.api; package net.woggioni.gbcs.api;
import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import java.nio.file.Path; import java.nio.file.Path;
@@ -23,25 +24,18 @@ public class Configuration {
@Value @Value
public static class Group { public static class Group {
@EqualsAndHashCode.Include
String name; String name;
Set<Role> roles; Set<Role> roles;
@Override
public int hashCode() {
return name.hashCode();
}
} }
@Value @Value
public static class User { public static class User {
@EqualsAndHashCode.Include
String name; String name;
String password; String password;
Set<Group> groups; Set<Group> groups;
@Override
public int hashCode() {
return name.hashCode();
}
public Set<Role> getRoles() { public Set<Role> getRoles() {
return groups.stream() return groups.stream()

View File

@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
id 'java-library' id 'java-library'
@@ -10,16 +8,6 @@ plugins {
dependencies { dependencies {
compileOnly project(':gbcs-api') compileOnly project(':gbcs-api')
compileOnly catalog.slf4j.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 { publishing {

View File

@@ -3,7 +3,6 @@ module net.woggioni.gbcs.base {
requires java.logging; requires java.logging;
requires org.slf4j; requires org.slf4j;
requires kotlin.stdlib; requires kotlin.stdlib;
requires net.woggioni.jwo;
exports net.woggioni.gbcs.base; exports net.woggioni.gbcs.base;
} }

View File

@@ -1,16 +1,20 @@
package net.woggioni.gbcs.base package net.woggioni.gbcs.base
import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.net.URL import java.net.URL
import java.net.URLConnection import java.net.URLConnection
import java.net.URLStreamHandler import java.net.URLStreamHandler
import java.net.URLStreamHandlerFactory import java.net.URLStreamHandlerFactory
import java.util.Optional
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.stream.Collectors import java.util.stream.Collectors
class ClasspathUrlStreamHandlerFactoryProvider : URLStreamHandlerFactory { class GbcsUrlStreamHandlerFactory : URLStreamHandlerFactory {
private class Handler(private val classLoader: ClassLoader = ClasspathUrlStreamHandlerFactoryProvider::class.java.classLoader) : URLStreamHandler() {
private class ClasspathHandler(private val classLoader: ClassLoader = GbcsUrlStreamHandlerFactory::class.java.classLoader) :
URLStreamHandler() {
override fun openConnection(u: URL): URLConnection? { override fun openConnection(u: URL): URLConnection? {
return javaClass.module return javaClass.module
@@ -20,23 +24,48 @@ class ClasspathUrlStreamHandlerFactoryProvider : URLStreamHandlerFactory {
val i = path.lastIndexOf('/') val i = path.lastIndexOf('/')
val packageName = path.substring(0, i).replace('/', '.') val packageName = path.substring(0, i).replace('/', '.')
val modules = packageMap[packageName]!! val modules = packageMap[packageName]!!
ModuleResourceURLConnection( ClasspathResourceURLConnection(
u, u,
modules modules
) )
} }
?: classLoader.getResource(u.path)?.let(URL::openConnection) ?: 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? { override fun createURLStreamHandler(protocol: String): URLStreamHandler? {
return when (protocol) { return when (protocol) {
"classpath" -> Handler() "classpath" -> ClasspathHandler()
"jpms" -> JpmsHandler()
else -> null else -> null
} }
} }
private class ModuleResourceURLConnection(url: URL?, private val modules: List<Module>) : private class ClasspathResourceURLConnection(url: URL?, private val modules: List<Module>) :
URLConnection(url) { URLConnection(url) {
override fun connect() {} override fun connect() {}
@@ -53,12 +82,12 @@ class ClasspathUrlStreamHandlerFactoryProvider : URLStreamHandlerFactory {
private val installed = AtomicBoolean(false) private val installed = AtomicBoolean(false)
fun install() { fun install() {
if (!installed.getAndSet(true)) { if (!installed.getAndSet(true)) {
URL.setURLStreamHandlerFactory(ClasspathUrlStreamHandlerFactoryProvider()) URL.setURLStreamHandlerFactory(GbcsUrlStreamHandlerFactory())
} }
} }
private val packageMap: Map<String, List<Module>> by lazy { private val packageMap: Map<String, List<Module>> by lazy {
ClasspathUrlStreamHandlerFactoryProvider::class.java.module.layer GbcsUrlStreamHandlerFactory::class.java.module.layer
.modules() .modules()
.stream() .stream()
.flatMap { m: Module -> .flatMap { m: Module ->

View File

@@ -18,8 +18,6 @@ Property<String> mainClassName = objects.property(String.class)
mainClassName.set('net.woggioni.gbcs.cli.GradleBuildCacheServerCli') mainClassName.set('net.woggioni.gbcs.cli.GradleBuildCacheServerCli')
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { 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 options.javaModuleMainClass = mainClassName
} }
@@ -36,8 +34,6 @@ dependencies {
implementation catalog.netty.codec.http implementation catalog.netty.codec.http
implementation catalog.picocli implementation catalog.picocli
// implementation project(':gbcs-base')
// implementation project(':gbcs-api')
implementation rootProject implementation rootProject
// runtimeOnly catalog.slf4j.jdk14 // runtimeOnly catalog.slf4j.jdk14
@@ -50,11 +46,6 @@ Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', E
systemProperties['logback.configurationFile'] = 'classpath:net/woggioni/gbcs/cli/logback.xml' 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) { tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
mainClass = 'net.woggioni.gbcs.GraalNativeImageConfiguration' mainClass = 'net.woggioni.gbcs.GraalNativeImageConfiguration'
} }
@@ -68,7 +59,7 @@ tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) {
publishing { publishing {
publications { publications {
maven(MavenPublication) { maven(MavenPublication) {
artifact envelopeJarArtifact artifact envelopeJar
} }
} }
} }

View File

@@ -2,7 +2,7 @@ package net.woggioni.gbcs.cli
import net.woggioni.gbcs.GradleBuildCacheServer import net.woggioni.gbcs.GradleBuildCacheServer
import net.woggioni.gbcs.GradleBuildCacheServer.Companion.DEFAULT_CONFIGURATION_URL 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.contextLogger
import net.woggioni.gbcs.base.debug import net.woggioni.gbcs.base.debug
import net.woggioni.gbcs.base.info import net.woggioni.gbcs.base.info
@@ -29,7 +29,7 @@ class GradleBuildCacheServerCli(application : Application, private val log : Log
@JvmStatic @JvmStatic
fun main(vararg args: String) { fun main(vararg args: String) {
Thread.currentThread().contextClassLoader = GradleBuildCacheServerCli::class.java.classLoader Thread.currentThread().contextClassLoader = GradleBuildCacheServerCli::class.java.classLoader
ClasspathUrlStreamHandlerFactoryProvider.install() GbcsUrlStreamHandlerFactory.install()
val log = contextLogger() val log = contextLogger()
val app = Application.builder("gbcs") val app = Application.builder("gbcs")
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR") .configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
@@ -92,7 +92,14 @@ class GradleBuildCacheServerCli(application : Application, private val log : Log
"Server configuration:\n${String(it.toByteArray())}" "Server configuration:\n${String(it.toByteArray())}"
} }
} }
GradleBuildCacheServer(configuration).run().use { val server = GradleBuildCacheServer(configuration)
server.run().use {
log.info {
"GradleBuildCacheServer is listening on ${configuration.host}:${configuration.port}"
}
}
log.info {
"GradleBuildCacheServer has been gracefully shut down"
} }
} }
} }

View File

@@ -30,15 +30,6 @@ dependencies {
implementation catalog.xmemcached 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<Tar> bundleTask = tasks.register("bundle", Tar) { Provider<Tar> bundleTask = tasks.register("bundle", Tar) {
from(tasks.named(JavaPlugin.JAR_TASK_NAME)) from(tasks.named(JavaPlugin.JAR_TASK_NAME))
from(configurations.bundle) from(configurations.bundle)
@@ -49,16 +40,10 @@ tasks.named(BasePlugin.ASSEMBLE_TASK_NAME) {
dependsOn(bundleTask) dependsOn(bundleTask)
} }
def bundleArtifact = artifacts.add('archives', bundleTask.get().archiveFile.get().asFile) {
type = 'tar'
builtBy bundleTask
}
publishing { publishing {
publications { publications {
maven(MavenPublication) { maven(MavenPublication) {
artifact bundleArtifact artifact bundleTask
} }
} }
} }

View File

@@ -1,9 +1,9 @@
org.gradle.configuration-cache=false org.gradle.configuration-cache=false
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=false org.gradle.caching=true
gbcs.version = 0.0.1 gbcs.version = 0.0.1
lys.version = 2025.01.09 lys.version = 2025.01.10
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven

View File

@@ -1,5 +1,4 @@
import net.woggioni.gbcs.api.CacheProvider; import net.woggioni.gbcs.api.CacheProvider;
import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider;
import net.woggioni.gbcs.cache.FileSystemCacheProvider; import net.woggioni.gbcs.cache.FileSystemCacheProvider;
module net.woggioni.gbcs { module net.woggioni.gbcs {
@@ -11,7 +10,6 @@ module net.woggioni.gbcs {
requires io.netty.buffer; requires io.netty.buffer;
requires io.netty.transport; requires io.netty.transport;
requires io.netty.codec.http; requires io.netty.codec.http;
requires io.netty.codec.http2;
requires io.netty.common; requires io.netty.common;
requires io.netty.handler; requires io.netty.handler;
requires io.netty.codec; requires io.netty.codec;
@@ -21,11 +19,10 @@ module net.woggioni.gbcs {
requires net.woggioni.gbcs.api; requires net.woggioni.gbcs.api;
exports net.woggioni.gbcs; exports net.woggioni.gbcs;
opens net.woggioni.gbcs; opens net.woggioni.gbcs;
opens net.woggioni.gbcs.schema; opens net.woggioni.gbcs.schema;
uses java.net.URLStreamHandlerFactory;
provides java.net.URLStreamHandlerFactory with ClasspathUrlStreamHandlerFactoryProvider;
uses CacheProvider; uses CacheProvider;
provides CacheProvider with FileSystemCacheProvider; provides CacheProvider with FileSystemCacheProvider;
} }

View File

@@ -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<Map<String, List<Module>>> 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, URLConnection>) 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<Module> modules;
ModuleResourceURLConnection(URL url, List<Module> 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;
}
}
}

View File

@@ -12,7 +12,6 @@ import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption import io.netty.channel.ChannelOption
import io.netty.channel.ChannelPromise import io.netty.channel.ChannelPromise
import io.netty.channel.DefaultFileRegion import io.netty.channel.DefaultFileRegion
import io.netty.channel.EventLoopGroup
import io.netty.channel.SimpleChannelInboundHandler import io.netty.channel.SimpleChannelInboundHandler
import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioServerSocketChannel import io.netty.channel.socket.nio.NioServerSocketChannel
@@ -34,9 +33,6 @@ import io.netty.handler.codec.http.HttpServerCodec
import io.netty.handler.codec.http.HttpUtil import io.netty.handler.codec.http.HttpUtil
import io.netty.handler.codec.http.HttpVersion import io.netty.handler.codec.http.HttpVersion
import io.netty.handler.codec.http.LastHttpContent 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 import io.netty.handler.ssl.ClientAuth
import io.netty.handler.ssl.SslContext import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder import io.netty.handler.ssl.SslContextBuilder
@@ -58,19 +54,12 @@ import net.woggioni.gbcs.base.PasswordSecurity.decodePasswordHash
import net.woggioni.gbcs.base.PasswordSecurity.hashPassword import net.woggioni.gbcs.base.PasswordSecurity.hashPassword
import net.woggioni.gbcs.base.Xml import net.woggioni.gbcs.base.Xml
import net.woggioni.gbcs.base.contextLogger 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.Parser
import net.woggioni.gbcs.configuration.Serializer 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.JWO
import net.woggioni.jwo.Tuple2 import net.woggioni.jwo.Tuple2
import java.io.ByteArrayOutputStream
import java.io.OutputStream import java.io.OutputStream
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.URL
import java.net.URLStreamHandlerFactory
import java.nio.channels.FileChannel import java.nio.channels.FileChannel
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
@@ -79,7 +68,6 @@ import java.security.PrivateKey
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.Arrays import java.util.Arrays
import java.util.Base64 import java.util.Base64
import java.util.concurrent.Executors
import java.util.regex.Matcher import java.util.regex.Matcher
import java.util.regex.Pattern import java.util.regex.Pattern
import javax.naming.ldap.LdapName import javax.naming.ldap.LdapName
@@ -188,55 +176,38 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
} }
} }
private class ServerInitializer(private val cfg: Configuration) : ChannelInitializer<Channel>() { private class ServerInitializer(
private val cfg: Configuration,
private fun createSslCtx(tls: Configuration.Tls): SslContext { private val eventExecutorGroup: EventExecutorGroup
val keyStore = tls.keyStore ) : ChannelInitializer<Channel>() {
return if (keyStore == null) {
throw IllegalArgumentException("No keystore configured")
} else {
val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
val serverKey = javaKeyStore.getKey(
keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray)
) as PrivateKey
val serverCert: Array<X509Certificate> =
Arrays.stream(javaKeyStore.getCertificateChain(keyStore.keyAlias))
.map { it as X509Certificate }
.toArray { size -> Array<X509Certificate?>(size) { null } }
SslContextBuilder.forServer(serverKey, *serverCert).apply {
if (tls.isVerifyClients) {
clientAuth(ClientAuth.OPTIONAL)
val trustStore = tls.trustStore
if (trustStore != null) {
val ts = loadKeystore(trustStore.file, trustStore.password)
trustManager(
ClientCertificateValidator.getTrustManager(ts, trustStore.isCheckCertificateStatus)
)
}
}
}.build()
}
}
private val sslContext: SslContext? = cfg.tls?.let(this::createSslCtx)
private val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
companion object { companion object {
private fun createSslCtx(tls: Configuration.Tls): SslContext {
private fun getServerAPNHandler(): ApplicationProtocolNegotiationHandler { val keyStore = tls.keyStore
val serverAPNHandler: ApplicationProtocolNegotiationHandler = return if (keyStore == null) {
object : ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_2) { throw IllegalArgumentException("No keystore configured")
override fun configurePipeline(ctx: ChannelHandlerContext, protocol: String) { } else {
if (ApplicationProtocolNames.HTTP_2 == protocol) { val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
ctx.pipeline().addLast( val serverKey = javaKeyStore.getKey(
Http2FrameCodecBuilder.forServer().build() keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray)
) as PrivateKey
val serverCert: Array<X509Certificate> =
Arrays.stream(javaKeyStore.getCertificateChain(keyStore.keyAlias))
.map { it as X509Certificate }
.toArray { size -> Array<X509Certificate?>(size) { null } }
SslContextBuilder.forServer(serverKey, *serverCert).apply {
if (tls.isVerifyClients) {
clientAuth(ClientAuth.OPTIONAL)
val trustStore = tls.trustStore
if (trustStore != null) {
val ts = loadKeystore(trustStore.file, trustStore.password)
trustManager(
ClientCertificateValidator.getTrustManager(ts, trustStore.isCheckCertificateStatus)
) )
return
} }
throw IllegalStateException("Protocol: $protocol not supported")
} }
} }.build()
return serverAPNHandler }
} }
fun loadKeystore(file: Path, password: String?): KeyStore { fun loadKeystore(file: Path, password: String?): KeyStore {
@@ -261,6 +232,8 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
} }
} }
private val sslContext: SslContext? = cfg.tls?.let(Companion::createSslCtx)
private fun userExtractor(authentication: Configuration.ClientCertificateAuthentication) = private fun userExtractor(authentication: Configuration.ClientCertificateAuthentication) =
authentication.userExtractor?.let { extractor -> authentication.userExtractor?.let { extractor ->
val pattern = Pattern.compile(extractor.pattern) val pattern = Pattern.compile(extractor.pattern)
@@ -299,6 +272,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
} }
if (sslContext != null) { if (sslContext != null) {
val sslHandler = sslContext.newHandler(ch.alloc()) val sslHandler = sslContext.newHandler(ch.alloc())
pipeline.addLast(sslHandler)
if (auth is Configuration.ClientCertificateAuthentication) { if (auth is Configuration.ClientCertificateAuthentication) {
val roleAuthorizer = RoleAuthorizer() val roleAuthorizer = RoleAuthorizer()
@@ -310,7 +284,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
) )
} }
} }
// pipeline.addLast(getServerAPNHandler())
pipeline.addLast(HttpServerCodec()) pipeline.addLast(HttpServerCodec())
pipeline.addLast(HttpChunkContentCompressor(1024)) pipeline.addLast(HttpChunkContentCompressor(1024))
pipeline.addLast(ChunkedWriteHandler()) pipeline.addLast(ChunkedWriteHandler())
@@ -320,7 +293,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
} }
val cacheImplementation = cfg.cache.materialize() val cacheImplementation = cfg.cache.materialize()
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/")) val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
pipeline.addLast(group, ServerHandler(cacheImplementation, prefix)) pipeline.addLast(eventExecutorGroup, ServerHandler(cacheImplementation, prefix))
pipeline.addLast(ExceptionHandler()) pipeline.addLast(ExceptionHandler())
} }
} }
@@ -370,20 +343,12 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
companion object { companion object {
private val log = contextLogger() private val log = contextLogger()
private fun splitPath(req: HttpRequest): Pair<String?, String> {
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) { override fun channelRead0(ctx: ChannelHandlerContext, msg: FullHttpRequest) {
val keepAlive: Boolean = HttpUtil.isKeepAlive(msg) val keepAlive: Boolean = HttpUtil.isKeepAlive(msg)
val method = msg.method() val method = msg.method()
if (method === HttpMethod.GET) { if (method === HttpMethod.GET) {
// val (prefix, key) = splitPath(msg)
val path = Path.of(msg.uri()) val path = Path.of(msg.uri())
val prefix = path.parent val prefix = path.parent
val key = path.fileName.toString() val key = path.fileName.toString()
@@ -479,46 +444,47 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
} }
class ServerHandle( class ServerHandle(
private val httpChannel: ChannelFuture, httpChannelFuture: ChannelFuture,
private val bossGroup: EventLoopGroup, private val executorGroups : Iterable<EventExecutorGroup>
private val workerGroup: EventLoopGroup
) : AutoCloseable { ) : AutoCloseable {
private val httpChannel: Channel = httpChannelFuture.channel()
private val closeFuture: ChannelFuture = httpChannel.channel().closeFuture() private val closeFuture: ChannelFuture = httpChannel.closeFuture()
fun shutdown(): ChannelFuture { fun shutdown(): ChannelFuture {
return httpChannel.channel().close() return httpChannel.close()
} }
override fun close() { override fun close() {
try { try {
closeFuture.sync() closeFuture.sync()
} finally { } finally {
val fut1 = workerGroup.shutdownGracefully() executorGroups.forEach {
val fut2 = if (bossGroup !== workerGroup) { it.shutdownGracefully().sync()
bossGroup.shutdownGracefully() }
} else null
fut1.sync()
fut2?.sync()
} }
} }
} }
fun run(): ServerHandle { fun run(): ServerHandle {
// Create the multithreaded event loops for the server // Create the multithreaded event loops for the server
val bossGroup = NioEventLoopGroup() val bossGroup = NioEventLoopGroup(0)
val serverSocketChannel = NioServerSocketChannel::class.java val serverSocketChannel = NioServerSocketChannel::class.java
val workerGroup = if (cfg.isUseVirtualThread) { val workerGroup = bossGroup
NioEventLoopGroup(0, Executors.newVirtualThreadPerTaskExecutor()) val eventExecutorGroup = run {
} else { val threadFactory = if(cfg.isUseVirtualThread) {
NioEventLoopGroup(0, Executors.newWorkStealingPool()) Thread.ofVirtual().factory()
} else {
null
}
DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors(), threadFactory)
} }
// A helper class that simplifies server configuration // A helper class that simplifies server configuration
val bootstrap = ServerBootstrap().apply { val bootstrap = ServerBootstrap().apply {
// Configure the server // Configure the server
group(bossGroup, workerGroup) group(bossGroup, workerGroup)
channel(serverSocketChannel) channel(serverSocketChannel)
childHandler(ServerInitializer(cfg)) childHandler(ServerInitializer(cfg, eventExecutorGroup))
option(ChannelOption.SO_BACKLOG, 128) option(ChannelOption.SO_BACKLOG, 128)
childOption(ChannelOption.SO_KEEPALIVE, true) childOption(ChannelOption.SO_KEEPALIVE, true)
} }
@@ -527,42 +493,13 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
// Bind and start to accept incoming connections. // Bind and start to accept incoming connections.
val bindAddress = InetSocketAddress(cfg.host, cfg.port) val bindAddress = InetSocketAddress(cfg.host, cfg.port)
val httpChannel = bootstrap.bind(bindAddress).sync() val httpChannel = bootstrap.bind(bindAddress).sync()
return ServerHandle(httpChannel, bossGroup, workerGroup) return ServerHandle(httpChannel, setOf(bossGroup, workerGroup, eventExecutorGroup))
} }
companion object { 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() } 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 { fun loadConfiguration(configurationFile: Path): Configuration {
val dbf = Xml.newDocumentBuilderFactory(null) val dbf = Xml.newDocumentBuilderFactory(null)
val db = dbf.newDocumentBuilder() val db = dbf.newDocumentBuilder()
@@ -570,64 +507,8 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
return Parser.parse(doc) return Parser.parse(doc)
} }
fun dumpConfiguration(conf : Configuration, outputStream: OutputStream) { fun dumpConfiguration(conf: Configuration, outputStream: OutputStream) {
Xml.write(Serializer.serialize(conf), outputStream) Xml.write(Serializer.serialize(conf), outputStream)
} }
fun loadConfiguration(args: Array<String>): 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<String>) {
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<String>) {
val conf = GradleBuildCacheServer.loadConfiguration(args)
GradleBuildCacheServer(conf).run().use {
Thread.sleep(3000)
it.shutdown()
}
}
}

View File

@@ -32,7 +32,7 @@ object Parser {
val serverPath = root.getAttribute("path") val serverPath = root.getAttribute("path")
val useVirtualThread = root.getAttribute("useVirtualThreads") val useVirtualThread = root.getAttribute("useVirtualThreads")
.takeIf(String::isNotEmpty) .takeIf(String::isNotEmpty)
?.let(String::toBoolean) ?: false ?.let(String::toBoolean) ?: true
var authentication: Authentication? = null var authentication: Authentication? = null
for (child in root.asIterable()) { for (child in root.asIterable()) {
when (child.localName) { when (child.localName) {

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" <gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gbcs="urn:net.woggioni.gbcs" xmlns:gbcs="urn:net.woggioni.gbcs"
xs:schemaLocation="urn:net.woggioni.gbcs classpath:net/woggioni/gbcs/schema/gbcs.xsd"> xs:schemaLocation="urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd">
<bind host="127.0.0.1" port="8080"/> <bind host="127.0.0.1" port="8080"/>
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/> <cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
<authentication> <authentication>

View File

@@ -10,9 +10,6 @@
<xs:sequence minOccurs="0"> <xs:sequence minOccurs="0">
<xs:element name="bind" type="gbcs:bindType" maxOccurs="1"/> <xs:element name="bind" type="gbcs:bindType" maxOccurs="1"/>
<xs:element name="cache" type="gbcs:cacheType" maxOccurs="1"/> <xs:element name="cache" type="gbcs:cacheType" maxOccurs="1"/>
<!-- <xs:choice>-->
<!-- <xs:element name="fileSystemCache" type="fileSystemCacheType"/>-->
<!-- </xs:choice>-->
<xs:element name="authorization" type="gbcs:authorizationType" minOccurs="0"> <xs:element name="authorization" type="gbcs:authorizationType" minOccurs="0">
<xs:key name="userId"> <xs:key name="userId">
<xs:selector xpath="users/user"/> <xs:selector xpath="users/user"/>
@@ -28,7 +25,7 @@
<xs:element name="tls" type="gbcs:tlsType" minOccurs="0" maxOccurs="1"/> <xs:element name="tls" type="gbcs:tlsType" minOccurs="0" maxOccurs="1"/>
</xs:sequence> </xs:sequence>
<xs:attribute name="path" type="xs:string" use="optional"/> <xs:attribute name="path" type="xs:string" use="optional"/>
<xs:attribute name="useVirtualThreads" type="xs:boolean" use="optional"/> <xs:attribute name="useVirtualThreads" type="xs:boolean" use="optional" default="true"/>
</xs:complexType> </xs:complexType>
<xs:complexType name="bindType"> <xs:complexType name="bindType">

View File

@@ -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");
}
}

View File

@@ -1,15 +1,17 @@
package net.woggioni.gbcs.test package net.woggioni.gbcs.test
import io.netty.handler.codec.http.HttpResponseStatus 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.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.cache.FileSystemCacheConfiguration
import net.woggioni.gbcs.configuration.Serializer import net.woggioni.gbcs.configuration.Serializer
import net.woggioni.gbcs.utils.NetworkUtils
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.io.IOException
import java.net.ServerSocket import java.net.ServerSocket
import java.net.URI import java.net.URI
import java.net.http.HttpClient import java.net.http.HttpClient
@@ -41,7 +43,7 @@ class BasicAuthServerTest : AbstractServerTest() {
val writersGroup = Configuration.Group("writers", setOf(Role.Writer)) val writersGroup = Configuration.Group("writers", setOf(Role.Writer))
cfg = Configuration( cfg = Configuration(
"127.0.0.1", "127.0.0.1",
ServerSocket(0).localPort + 1, NetworkUtils.getFreePort(),
serverPath, serverPath,
listOf( listOf(
Configuration.User("user1", hashPassword(PASSWORD), setOf(readersGroup)), Configuration.User("user1", hashPassword(PASSWORD), setOf(readersGroup)),

View File

@@ -1,10 +1,10 @@
package net.woggioni.gbcs.test package net.woggioni.gbcs.test
import net.woggioni.gbcs.base.GbcsUrlStreamHandlerFactory
import net.woggioni.gbcs.base.GBCS.toUrl import net.woggioni.gbcs.base.GBCS.toUrl
import net.woggioni.gbcs.base.Xml import net.woggioni.gbcs.base.Xml
import net.woggioni.gbcs.configuration.Parser import net.woggioni.gbcs.configuration.Parser
import net.woggioni.gbcs.configuration.Serializer import net.woggioni.gbcs.configuration.Serializer
import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.api.io.TempDir
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
@@ -22,7 +22,7 @@ class ConfigurationTest {
) )
@ParameterizedTest @ParameterizedTest
fun test(configurationUrl: String, @TempDir testDir: Path) { fun test(configurationUrl: String, @TempDir testDir: Path) {
ClasspathUrlStreamHandlerFactoryProvider.install() GbcsUrlStreamHandlerFactory.install()
val doc = Xml.parseXml(configurationUrl.toUrl()) val doc = Xml.parseXml(configurationUrl.toUrl())
val cfg = Parser.parse(doc) val cfg = Parser.parse(doc)
val configFile = testDir.resolve("gbcs.xml") val configFile = testDir.resolve("gbcs.xml")

View File

@@ -5,6 +5,7 @@ import net.woggioni.gbcs.base.Xml
import net.woggioni.gbcs.api.Configuration import net.woggioni.gbcs.api.Configuration
import net.woggioni.gbcs.cache.FileSystemCacheConfiguration import net.woggioni.gbcs.cache.FileSystemCacheConfiguration
import net.woggioni.gbcs.configuration.Serializer import net.woggioni.gbcs.configuration.Serializer
import net.woggioni.gbcs.utils.NetworkUtils
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -32,7 +33,7 @@ class NoAuthServerTest : AbstractServerTest() {
this.cacheDir = testDir.resolve("cache") this.cacheDir = testDir.resolve("cache")
cfg = Configuration( cfg = Configuration(
"127.0.0.1", "127.0.0.1",
ServerSocket(0).localPort + 1, NetworkUtils.getFreePort(),
serverPath, serverPath,
emptyMap(), emptyMap(),
emptyMap(), emptyMap(),
@@ -104,4 +105,28 @@ class NoAuthServerTest : AbstractServerTest() {
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()) val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode()) Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
} }
// @Test
// @Order(4)
// fun manyRequestsTest() {
// val client: HttpClient = HttpClient.newHttpClient()
//
// for(i in 0 until 100000) {
//
// val newEntry = random.nextBoolean()
// val (key, _) = if(newEntry) {
// newEntry(random)
// } else {
// keyValuePair
// }
// val requestBuilder = newRequestBuilder(key).GET()
//
// val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
// if(newEntry) {
// Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
// } else {
// Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
// }
// }
// }
} }

View File

@@ -8,6 +8,7 @@ import net.woggioni.gbcs.cache.FileSystemCacheConfiguration
import net.woggioni.gbcs.configuration.Serializer import net.woggioni.gbcs.configuration.Serializer
import net.woggioni.gbcs.utils.CertificateUtils import net.woggioni.gbcs.utils.CertificateUtils
import net.woggioni.gbcs.utils.CertificateUtils.X509Credentials import net.woggioni.gbcs.utils.CertificateUtils.X509Credentials
import net.woggioni.gbcs.utils.NetworkUtils
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order import org.junit.jupiter.api.Order
@@ -146,7 +147,7 @@ class TlsServerTest : AbstractServerTest() {
createKeyStoreAndTrustStore() createKeyStoreAndTrustStore()
cfg = Configuration( cfg = Configuration(
"127.0.0.1", "127.0.0.1",
ServerSocket(0).localPort + 1, NetworkUtils.getFreePort(),
serverPath, serverPath,
users.asSequence().map { it.name to it }.toMap(), users.asSequence().map { it.name to it }.toMap(),
sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(), sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(),
@@ -227,7 +228,6 @@ class TlsServerTest : AbstractServerTest() {
@Test @Test
@Order(3) @Order(3)
fun getAsAWriterUser() { fun getAsAWriterUser() {
val (key, _) = keyValuePair val (key, _) = keyValuePair
val user = cfg.users.values.find { val user = cfg.users.values.find {
Role.Writer in it.roles Role.Writer in it.roles
@@ -245,7 +245,6 @@ class TlsServerTest : AbstractServerTest() {
@Test @Test
@Order(4) @Order(4)
fun putAsAWriterUser() { fun putAsAWriterUser() {
val (key, value) = keyValuePair val (key, value) = keyValuePair
val user = cfg.users.values.find { val user = cfg.users.values.find {
Role.Writer in it.roles Role.Writer in it.roles

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>
<configuration>
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
<import class="ch.qos.logback.core.ConsoleAppender"/>
<appender name="console" class="ConsoleAppender">
<target>System.err</target>
<encoder class="PatternLayoutEncoder">
<pattern>%d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console"/>
</root>
<logger name="io.netty" level="debug"/>
<logger name="com.google.code.yanf4j" level="warn"/>
<logger name="net.rubyeye.xmemcached" level="warn"/>
</configuration>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" <gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gbcs="urn:net.woggioni.gbcs" xmlns:gbcs="urn:net.woggioni.gbcs"
xs:schemaLocation="urn:net.woggioni.gbcs classpath:net/woggioni/gbcs/schema/gbcs.xsd"> xs:schemaLocation="urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd">
<bind host="127.0.0.1" port="11443"/> <bind host="127.0.0.1" port="11443"/>
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/> <cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
<authentication> <authentication>

View File

@@ -2,7 +2,7 @@
<gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" <gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gbcs="urn:net.woggioni.gbcs" xmlns:gbcs="urn:net.woggioni.gbcs"
xmlns:gbcs-memcached="urn:net.woggioni.gbcs-memcached" xmlns:gbcs-memcached="urn:net.woggioni.gbcs-memcached"
xs:schemaLocation="urn:net.woggioni.gbcs classpath:net/woggioni/gbcs/schema/gbcs.xsd urn:net.woggioni.gbcs-memcached classpath:net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd"> 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">
<bind host="127.0.0.1" port="11443" /> <bind host="127.0.0.1" port="11443" />
<cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="101325" compression-mode="gzip" digest="SHA-256"> <cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="101325" compression-mode="gzip" digest="SHA-256">
<server host="127.0.0.1" port="11211"/> <server host="127.0.0.1" port="11211"/>