This commit is contained in:
@@ -14,6 +14,3 @@ WORKDIR /home/luser/plugins
|
|||||||
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/gbcs-server-memcached*.tar
|
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/gbcs-server-memcached*.tar
|
||||||
WORKDIR /home/luser
|
WORKDIR /home/luser
|
||||||
ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar", "server"]
|
ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar", "server"]
|
||||||
|
|
||||||
FROM release-memcached as compose
|
|
||||||
COPY --chown=luser:luser conf/gbcs-memcached.xml /home/luser/.config/gbcs/gbcs.xml
|
|
@@ -2,10 +2,12 @@ package net.woggioni.gbcs.api;
|
|||||||
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -14,15 +16,32 @@ import java.util.stream.Collectors;
|
|||||||
public class Configuration {
|
public class Configuration {
|
||||||
String host;
|
String host;
|
||||||
int port;
|
int port;
|
||||||
|
int incomingConnectionsBacklogSize;
|
||||||
String serverPath;
|
String serverPath;
|
||||||
|
@NonNull
|
||||||
|
EventExecutor eventExecutor;
|
||||||
|
@NonNull
|
||||||
|
Connection connection;
|
||||||
Map<String, User> users;
|
Map<String, User> users;
|
||||||
Map<String, Group> groups;
|
Map<String, Group> groups;
|
||||||
Cache cache;
|
Cache cache;
|
||||||
Authentication authentication;
|
Authentication authentication;
|
||||||
Tls tls;
|
Tls tls;
|
||||||
boolean useVirtualThread;
|
|
||||||
int maxRequestSize;
|
@Value
|
||||||
int incomingConnectionsBacklogSize;
|
public static class EventExecutor {
|
||||||
|
boolean useVirtualThreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public static class Connection {
|
||||||
|
Duration readTimeout;
|
||||||
|
Duration writeTimeout;
|
||||||
|
Duration idleTimeout;
|
||||||
|
Duration readIdleTimeout;
|
||||||
|
Duration writeIdleTimeout;
|
||||||
|
int maxRequestSize;
|
||||||
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public static class Group {
|
public static class Group {
|
||||||
@@ -103,28 +122,28 @@ public class Configuration {
|
|||||||
public static Configuration of(
|
public static Configuration of(
|
||||||
String host,
|
String host,
|
||||||
int port,
|
int port,
|
||||||
|
int incomingConnectionsBacklogSize,
|
||||||
String serverPath,
|
String serverPath,
|
||||||
|
EventExecutor eventExecutor,
|
||||||
|
Connection connection,
|
||||||
Map<String, User> users,
|
Map<String, User> users,
|
||||||
Map<String, Group> groups,
|
Map<String, Group> groups,
|
||||||
Cache cache,
|
Cache cache,
|
||||||
Authentication authentication,
|
Authentication authentication,
|
||||||
Tls tls,
|
Tls tls
|
||||||
boolean useVirtualThread,
|
|
||||||
int maxRequestSize,
|
|
||||||
int incomingConnectionsBacklogSize
|
|
||||||
) {
|
) {
|
||||||
return new Configuration(
|
return new Configuration(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
incomingConnectionsBacklogSize,
|
||||||
serverPath != null && !serverPath.isEmpty() && !serverPath.equals("/") ? serverPath : null,
|
serverPath != null && !serverPath.isEmpty() && !serverPath.equals("/") ? serverPath : null,
|
||||||
|
eventExecutor,
|
||||||
|
connection,
|
||||||
users,
|
users,
|
||||||
groups,
|
groups,
|
||||||
cache,
|
cache,
|
||||||
authentication,
|
authentication,
|
||||||
tls,
|
tls
|
||||||
useVirtualThread,
|
|
||||||
maxRequestSize,
|
|
||||||
incomingConnectionsBacklogSize
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<xs:complexType name="profileType">
|
<xs:complexType name="profileType">
|
||||||
<xs:choice>
|
<xs:choice>
|
||||||
|
<xs:element name="no-auth" type="gbcs-client:noAuthType"/>
|
||||||
<xs:element name="basic-auth" type="gbcs-client:basicAuthType"/>
|
<xs:element name="basic-auth" type="gbcs-client:basicAuthType"/>
|
||||||
<xs:element name="tls-client-auth" type="gbcs-client:tlsClientAuthType"/>
|
<xs:element name="tls-client-auth" type="gbcs-client:tlsClientAuthType"/>
|
||||||
</xs:choice>
|
</xs:choice>
|
||||||
@@ -22,6 +23,8 @@
|
|||||||
<xs:attribute name="max-connections" type="xs:positiveInteger" default="50"/>
|
<xs:attribute name="max-connections" type="xs:positiveInteger" default="50"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="noAuthType"/>
|
||||||
|
|
||||||
<xs:complexType name="basicAuthType">
|
<xs:complexType name="basicAuthType">
|
||||||
<xs:attribute name="user" type="xs:token" use="required"/>
|
<xs:attribute name="user" type="xs:token" use="required"/>
|
||||||
<xs:attribute name="password" type="xs:string" use="required"/>
|
<xs:attribute name="password" type="xs:string" use="required"/>
|
||||||
|
@@ -9,41 +9,37 @@ import io.netty.channel.ChannelFuture
|
|||||||
import io.netty.channel.ChannelFutureListener
|
import io.netty.channel.ChannelFutureListener
|
||||||
import io.netty.channel.ChannelHandler.Sharable
|
import io.netty.channel.ChannelHandler.Sharable
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter
|
||||||
import io.netty.channel.ChannelInitializer
|
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.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
|
||||||
import io.netty.handler.codec.DecoderException
|
import io.netty.handler.codec.DecoderException
|
||||||
import io.netty.handler.codec.compression.CompressionOptions
|
import io.netty.handler.codec.compression.CompressionOptions
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
||||||
import io.netty.handler.codec.http.DefaultHttpContent
|
import io.netty.handler.codec.http.DefaultHttpContent
|
||||||
import io.netty.handler.codec.http.DefaultHttpResponse
|
|
||||||
import io.netty.handler.codec.http.FullHttpRequest
|
|
||||||
import io.netty.handler.codec.http.FullHttpResponse
|
import io.netty.handler.codec.http.FullHttpResponse
|
||||||
import io.netty.handler.codec.http.HttpContentCompressor
|
import io.netty.handler.codec.http.HttpContentCompressor
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames
|
import io.netty.handler.codec.http.HttpHeaderNames
|
||||||
import io.netty.handler.codec.http.HttpHeaderValues
|
|
||||||
import io.netty.handler.codec.http.HttpMethod
|
|
||||||
import io.netty.handler.codec.http.HttpObjectAggregator
|
import io.netty.handler.codec.http.HttpObjectAggregator
|
||||||
import io.netty.handler.codec.http.HttpRequest
|
import io.netty.handler.codec.http.HttpRequest
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
import io.netty.handler.codec.http.HttpServerCodec
|
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.HttpVersion
|
||||||
import io.netty.handler.codec.http.LastHttpContent
|
|
||||||
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
|
||||||
import io.netty.handler.ssl.SslHandler
|
import io.netty.handler.ssl.SslHandler
|
||||||
import io.netty.handler.stream.ChunkedNioFile
|
|
||||||
import io.netty.handler.stream.ChunkedNioStream
|
|
||||||
import io.netty.handler.stream.ChunkedWriteHandler
|
import io.netty.handler.stream.ChunkedWriteHandler
|
||||||
|
import io.netty.handler.timeout.IdleStateEvent
|
||||||
|
import io.netty.handler.timeout.IdleStateHandler
|
||||||
|
import io.netty.handler.timeout.ReadTimeoutException
|
||||||
|
import io.netty.handler.timeout.ReadTimeoutHandler
|
||||||
|
import io.netty.handler.timeout.WriteTimeoutException
|
||||||
|
import io.netty.handler.timeout.WriteTimeoutHandler
|
||||||
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
||||||
import io.netty.util.concurrent.EventExecutorGroup
|
import io.netty.util.concurrent.EventExecutorGroup
|
||||||
import net.woggioni.gbcs.api.Cache
|
|
||||||
import net.woggioni.gbcs.api.Configuration
|
import net.woggioni.gbcs.api.Configuration
|
||||||
import net.woggioni.gbcs.api.Role
|
import net.woggioni.gbcs.api.Role
|
||||||
import net.woggioni.gbcs.api.exception.ConfigurationException
|
import net.woggioni.gbcs.api.exception.ConfigurationException
|
||||||
@@ -53,6 +49,7 @@ import net.woggioni.gbcs.common.PasswordSecurity.decodePasswordHash
|
|||||||
import net.woggioni.gbcs.common.PasswordSecurity.hashPassword
|
import net.woggioni.gbcs.common.PasswordSecurity.hashPassword
|
||||||
import net.woggioni.gbcs.common.Xml
|
import net.woggioni.gbcs.common.Xml
|
||||||
import net.woggioni.gbcs.common.contextLogger
|
import net.woggioni.gbcs.common.contextLogger
|
||||||
|
import net.woggioni.gbcs.common.debug
|
||||||
import net.woggioni.gbcs.common.info
|
import net.woggioni.gbcs.common.info
|
||||||
import net.woggioni.gbcs.server.auth.AbstractNettyHttpAuthenticator
|
import net.woggioni.gbcs.server.auth.AbstractNettyHttpAuthenticator
|
||||||
import net.woggioni.gbcs.server.auth.Authorizer
|
import net.woggioni.gbcs.server.auth.Authorizer
|
||||||
@@ -60,11 +57,11 @@ import net.woggioni.gbcs.server.auth.ClientCertificateValidator
|
|||||||
import net.woggioni.gbcs.server.auth.RoleAuthorizer
|
import net.woggioni.gbcs.server.auth.RoleAuthorizer
|
||||||
import net.woggioni.gbcs.server.configuration.Parser
|
import net.woggioni.gbcs.server.configuration.Parser
|
||||||
import net.woggioni.gbcs.server.configuration.Serializer
|
import net.woggioni.gbcs.server.configuration.Serializer
|
||||||
|
import net.woggioni.gbcs.server.handler.ServerHandler
|
||||||
import net.woggioni.jwo.JWO
|
import net.woggioni.jwo.JWO
|
||||||
import net.woggioni.jwo.Tuple2
|
import net.woggioni.jwo.Tuple2
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
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
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
@@ -72,6 +69,7 @@ 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.TimeUnit
|
||||||
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
|
||||||
@@ -200,28 +198,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
private val eventExecutorGroup: EventExecutorGroup
|
private val eventExecutorGroup: EventExecutorGroup
|
||||||
) : ChannelInitializer<Channel>() {
|
) : ChannelInitializer<Channel>() {
|
||||||
|
|
||||||
private val serverHandler = let {
|
|
||||||
val cacheImplementation = cfg.cache.materialize()
|
|
||||||
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
|
|
||||||
ServerHandler(cacheImplementation, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val exceptionHandler = ExceptionHandler()
|
|
||||||
|
|
||||||
private val authenticator = when (val auth = cfg.authentication) {
|
|
||||||
is Configuration.BasicAuthentication -> NettyHttpBasicAuthenticator(cfg.users, RoleAuthorizer())
|
|
||||||
is Configuration.ClientCertificateAuthentication -> {
|
|
||||||
ClientCertificateAuthenticator(
|
|
||||||
RoleAuthorizer(),
|
|
||||||
cfg.users[""]?.roles,
|
|
||||||
userExtractor(auth),
|
|
||||||
groupExtractor(auth)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun createSslCtx(tls: Configuration.Tls): SslContext {
|
private fun createSslCtx(tls: Configuration.Tls): SslContext {
|
||||||
val keyStore = tls.keyStore
|
val keyStore = tls.keyStore
|
||||||
@@ -272,6 +248,30 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val log = contextLogger()
|
||||||
|
|
||||||
|
private val serverHandler = let {
|
||||||
|
val cacheImplementation = cfg.cache.materialize()
|
||||||
|
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
|
||||||
|
ServerHandler(cacheImplementation, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val exceptionHandler = ExceptionHandler()
|
||||||
|
|
||||||
|
private val authenticator = when (val auth = cfg.authentication) {
|
||||||
|
is Configuration.BasicAuthentication -> NettyHttpBasicAuthenticator(cfg.users, RoleAuthorizer())
|
||||||
|
is Configuration.ClientCertificateAuthentication -> {
|
||||||
|
ClientCertificateAuthenticator(
|
||||||
|
RoleAuthorizer(),
|
||||||
|
cfg.users[""]?.roles,
|
||||||
|
userExtractor(auth),
|
||||||
|
groupExtractor(auth)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
private val sslContext: SslContext? = cfg.tls?.let(Companion::createSslCtx)
|
private val sslContext: SslContext? = cfg.tls?.let(Companion::createSslCtx)
|
||||||
|
|
||||||
private fun userExtractor(authentication: Configuration.ClientCertificateAuthentication) =
|
private fun userExtractor(authentication: Configuration.ClientCertificateAuthentication) =
|
||||||
@@ -303,14 +303,37 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initChannel(ch: Channel) {
|
override fun initChannel(ch: Channel) {
|
||||||
|
log.debug {
|
||||||
|
"Created connection ${ch.id().asShortText()}"
|
||||||
|
}
|
||||||
|
ch.closeFuture().addListener {
|
||||||
|
log.debug {
|
||||||
|
"Closed connection ${ch.id().asShortText()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
val pipeline = ch.pipeline()
|
val pipeline = ch.pipeline()
|
||||||
|
cfg.connection.apply {
|
||||||
|
pipeline.addLast(ReadTimeoutHandler(readTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||||
|
pipeline.addLast(WriteTimeoutHandler(writeTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||||
|
pipeline.addLast(IdleStateHandler(false, 0, 0, idleTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||||
|
}
|
||||||
|
pipeline.addLast(object : ChannelInboundHandlerAdapter() {
|
||||||
|
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
||||||
|
if (evt is IdleStateEvent) {
|
||||||
|
log.debug {
|
||||||
|
"Idle timeout reached on channel ${ch.id().asShortText()}, closing the connection"
|
||||||
|
}
|
||||||
|
ctx.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
sslContext?.newHandler(ch.alloc())?.also {
|
sslContext?.newHandler(ch.alloc())?.also {
|
||||||
pipeline.addLast(SSL_HANDLER_NAME, it)
|
pipeline.addLast(SSL_HANDLER_NAME, it)
|
||||||
}
|
}
|
||||||
pipeline.addLast(HttpServerCodec())
|
pipeline.addLast(HttpServerCodec())
|
||||||
pipeline.addLast(HttpChunkContentCompressor(1024))
|
pipeline.addLast(HttpChunkContentCompressor(1024))
|
||||||
pipeline.addLast(ChunkedWriteHandler())
|
pipeline.addLast(ChunkedWriteHandler())
|
||||||
pipeline.addLast(HttpObjectAggregator(cfg.maxRequestSize))
|
pipeline.addLast(HttpObjectAggregator(cfg.connection.maxRequestSize))
|
||||||
authenticator?.let {
|
authenticator?.let {
|
||||||
pipeline.addLast(it)
|
pipeline.addLast(it)
|
||||||
}
|
}
|
||||||
@@ -351,7 +374,20 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
ctx.writeAndFlush(TOO_BIG.retainedDuplicate())
|
ctx.writeAndFlush(TOO_BIG.retainedDuplicate())
|
||||||
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
|
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
|
||||||
}
|
}
|
||||||
|
is ReadTimeoutException -> {
|
||||||
|
log.debug {
|
||||||
|
val channelId = ctx.channel().id().asShortText()
|
||||||
|
"Read timeout on channel $channelId, closing the connection"
|
||||||
|
}
|
||||||
|
ctx.close()
|
||||||
|
}
|
||||||
|
is WriteTimeoutException -> {
|
||||||
|
log.debug {
|
||||||
|
val channelId = ctx.channel().id().asShortText()
|
||||||
|
"Write timeout on channel $channelId, closing the connection"
|
||||||
|
}
|
||||||
|
ctx.close()
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
log.error(cause.message, cause)
|
log.error(cause.message, cause)
|
||||||
ctx.close()
|
ctx.close()
|
||||||
@@ -360,110 +396,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Sharable
|
|
||||||
private class ServerHandler(private val cache: Cache, private val serverPrefix: Path) :
|
|
||||||
SimpleChannelInboundHandler<FullHttpRequest>() {
|
|
||||||
|
|
||||||
private val log = contextLogger()
|
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: FullHttpRequest) {
|
|
||||||
val keepAlive: Boolean = HttpUtil.isKeepAlive(msg)
|
|
||||||
val method = msg.method()
|
|
||||||
if (method === HttpMethod.GET) {
|
|
||||||
val path = Path.of(msg.uri())
|
|
||||||
val prefix = path.parent
|
|
||||||
val key = path.fileName.toString()
|
|
||||||
if (serverPrefix == prefix) {
|
|
||||||
cache.get(key)?.let { channel ->
|
|
||||||
log.debug(ctx) {
|
|
||||||
"Cache hit for key '$key'"
|
|
||||||
}
|
|
||||||
val response = DefaultHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK)
|
|
||||||
response.headers()[HttpHeaderNames.CONTENT_TYPE] = HttpHeaderValues.APPLICATION_OCTET_STREAM
|
|
||||||
if (!keepAlive) {
|
|
||||||
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE)
|
|
||||||
response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.IDENTITY)
|
|
||||||
} else {
|
|
||||||
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE)
|
|
||||||
response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED)
|
|
||||||
}
|
|
||||||
ctx.write(response)
|
|
||||||
when (channel) {
|
|
||||||
is FileChannel -> {
|
|
||||||
if (keepAlive) {
|
|
||||||
ctx.write(ChunkedNioFile(channel))
|
|
||||||
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT)
|
|
||||||
} else {
|
|
||||||
ctx.writeAndFlush(DefaultFileRegion(channel, 0, channel.size()))
|
|
||||||
.addListener(ChannelFutureListener.CLOSE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
ctx.write(ChunkedNioStream(channel))
|
|
||||||
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: let {
|
|
||||||
log.debug(ctx) {
|
|
||||||
"Cache miss for key '$key'"
|
|
||||||
}
|
|
||||||
val response = DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.NOT_FOUND)
|
|
||||||
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
|
||||||
ctx.writeAndFlush(response)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn(ctx) {
|
|
||||||
"Got request for unhandled path '${msg.uri()}'"
|
|
||||||
}
|
|
||||||
val response = DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.BAD_REQUEST)
|
|
||||||
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
|
||||||
ctx.writeAndFlush(response)
|
|
||||||
}
|
|
||||||
} else if (method === HttpMethod.PUT) {
|
|
||||||
val path = Path.of(msg.uri())
|
|
||||||
val prefix = path.parent
|
|
||||||
val key = path.fileName.toString()
|
|
||||||
|
|
||||||
if (serverPrefix == prefix) {
|
|
||||||
log.debug(ctx) {
|
|
||||||
"Added value for key '$key' to build cache"
|
|
||||||
}
|
|
||||||
val bodyBytes = msg.content().run {
|
|
||||||
if (isDirect) {
|
|
||||||
ByteArray(readableBytes()).also {
|
|
||||||
readBytes(it)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
array()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cache.put(key, bodyBytes)
|
|
||||||
val response = DefaultFullHttpResponse(
|
|
||||||
msg.protocolVersion(), HttpResponseStatus.CREATED,
|
|
||||||
Unpooled.copiedBuffer(key.toByteArray())
|
|
||||||
)
|
|
||||||
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = response.content().readableBytes()
|
|
||||||
ctx.writeAndFlush(response)
|
|
||||||
} else {
|
|
||||||
log.warn(ctx) {
|
|
||||||
"Got request for unhandled path '${msg.uri()}'"
|
|
||||||
}
|
|
||||||
val response = DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.BAD_REQUEST)
|
|
||||||
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
|
|
||||||
ctx.writeAndFlush(response)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn(ctx) {
|
|
||||||
"Got request with unhandled method '${msg.method().name()}'"
|
|
||||||
}
|
|
||||||
val response = DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.BAD_REQUEST)
|
|
||||||
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
|
|
||||||
ctx.writeAndFlush(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ServerHandle(
|
class ServerHandle(
|
||||||
httpChannelFuture: ChannelFuture,
|
httpChannelFuture: ChannelFuture,
|
||||||
private val executorGroups: Iterable<EventExecutorGroup>
|
private val executorGroups: Iterable<EventExecutorGroup>
|
||||||
@@ -496,7 +428,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
val serverSocketChannel = NioServerSocketChannel::class.java
|
val serverSocketChannel = NioServerSocketChannel::class.java
|
||||||
val workerGroup = bossGroup
|
val workerGroup = bossGroup
|
||||||
val eventExecutorGroup = run {
|
val eventExecutorGroup = run {
|
||||||
val threadFactory = if (cfg.isUseVirtualThread) {
|
val threadFactory = if (cfg.eventExecutor.isUseVirtualThreads) {
|
||||||
Thread.ofVirtual().factory()
|
Thread.ofVirtual().factory()
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@@ -19,11 +19,16 @@ import org.w3c.dom.Document
|
|||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.TypeInfo
|
import org.w3c.dom.TypeInfo
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object Parser {
|
object Parser {
|
||||||
fun parse(document: Document): Configuration {
|
fun parse(document: Document): Configuration {
|
||||||
val root = document.documentElement
|
val root = document.documentElement
|
||||||
val anonymousUser = User("", null, emptySet())
|
val anonymousUser = User("", null, emptySet())
|
||||||
|
var connection: Configuration.Connection? = null
|
||||||
|
var eventExecutor: Configuration.EventExecutor? = null
|
||||||
var cache: Cache? = null
|
var cache: Cache? = null
|
||||||
var host = "127.0.0.1"
|
var host = "127.0.0.1"
|
||||||
var port = 11080
|
var port = 11080
|
||||||
@@ -31,46 +36,11 @@ object Parser {
|
|||||||
var groups = emptyMap<String, Group>()
|
var groups = emptyMap<String, Group>()
|
||||||
var tls: Tls? = null
|
var tls: Tls? = null
|
||||||
val serverPath = root.renderAttribute("path")
|
val serverPath = root.renderAttribute("path")
|
||||||
val useVirtualThread = root.renderAttribute("use-virtual-threads")
|
var incomingConnectionsBacklogSize = 1024
|
||||||
?.let(String::toBoolean) ?: true
|
|
||||||
val maxRequestSize = root.renderAttribute("max-request-size")
|
|
||||||
?.let(String::toInt) ?: 67108864
|
|
||||||
val incomingConnectionsBacklogSize = root.renderAttribute("incoming-connections-backlog-size")
|
|
||||||
?.let(String::toInt) ?: 1024
|
|
||||||
var authentication: Authentication? = null
|
var authentication: Authentication? = null
|
||||||
for (child in root.asIterable()) {
|
for (child in root.asIterable()) {
|
||||||
val tagName = child.localName
|
val tagName = child.localName
|
||||||
when (tagName) {
|
when (tagName) {
|
||||||
"authorization" -> {
|
|
||||||
var knownUsers = sequenceOf(anonymousUser)
|
|
||||||
for (gchild in child.asIterable()) {
|
|
||||||
when (gchild.localName) {
|
|
||||||
"users" -> {
|
|
||||||
knownUsers += parseUsers(gchild)
|
|
||||||
}
|
|
||||||
"groups" -> {
|
|
||||||
val pair = parseGroups(gchild, knownUsers)
|
|
||||||
users = pair.first
|
|
||||||
groups = pair.second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"bind" -> {
|
|
||||||
host = child.renderAttribute("host") ?: throw ConfigurationException("host attribute is required")
|
|
||||||
port = Integer.parseInt(child.renderAttribute("port"))
|
|
||||||
}
|
|
||||||
|
|
||||||
"cache" -> {
|
|
||||||
cache = (child as TypeInfo).let { tf ->
|
|
||||||
val typeNamespace = tf.typeNamespace
|
|
||||||
val typeName = tf.typeName
|
|
||||||
CacheSerializers.index[typeNamespace to typeName]
|
|
||||||
?: throw IllegalArgumentException("Cache provider for namespace '$typeNamespace' not found")
|
|
||||||
}.deserialize(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
"authentication" -> {
|
"authentication" -> {
|
||||||
for (gchild in child.asIterable()) {
|
for (gchild in child.asIterable()) {
|
||||||
when (gchild.localName) {
|
when (gchild.localName) {
|
||||||
@@ -102,6 +72,66 @@ object Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"authorization" -> {
|
||||||
|
var knownUsers = sequenceOf(anonymousUser)
|
||||||
|
for (gchild in child.asIterable()) {
|
||||||
|
when (gchild.localName) {
|
||||||
|
"users" -> {
|
||||||
|
knownUsers += parseUsers(gchild)
|
||||||
|
}
|
||||||
|
"groups" -> {
|
||||||
|
val pair = parseGroups(gchild, knownUsers)
|
||||||
|
users = pair.first
|
||||||
|
groups = pair.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"bind" -> {
|
||||||
|
host = child.renderAttribute("host") ?: throw ConfigurationException("host attribute is required")
|
||||||
|
port = Integer.parseInt(child.renderAttribute("port"))
|
||||||
|
incomingConnectionsBacklogSize = child.renderAttribute("incoming-connections-backlog-size")
|
||||||
|
?.let(Integer::parseInt)
|
||||||
|
?: 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
"cache" -> {
|
||||||
|
cache = (child as TypeInfo).let { tf ->
|
||||||
|
val typeNamespace = tf.typeNamespace
|
||||||
|
val typeName = tf.typeName
|
||||||
|
CacheSerializers.index[typeNamespace to typeName]
|
||||||
|
?: throw IllegalArgumentException("Cache provider for namespace '$typeNamespace' not found")
|
||||||
|
}.deserialize(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
"connection" -> {
|
||||||
|
val writeTimeout = child.renderAttribute("write-timeout")
|
||||||
|
?.let(Duration::parse) ?: Duration.of(10, ChronoUnit.SECONDS)
|
||||||
|
val readTimeout = child.renderAttribute("read-timeout")
|
||||||
|
?.let(Duration::parse) ?: Duration.of(10, ChronoUnit.SECONDS)
|
||||||
|
val idleTimeout = child.renderAttribute("idle-timeout")
|
||||||
|
?.let(Duration::parse) ?: Duration.of(30, ChronoUnit.SECONDS)
|
||||||
|
val readIdleTimeout = child.renderAttribute("read-idle-timeout")
|
||||||
|
?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS)
|
||||||
|
val writeIdleTimeout = child.renderAttribute("write-idle-timeout")
|
||||||
|
?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS)
|
||||||
|
val maxRequestSize = child.renderAttribute("max-request-size")
|
||||||
|
?.let(String::toInt) ?: 67108864
|
||||||
|
connection = Configuration.Connection(
|
||||||
|
readTimeout,
|
||||||
|
writeTimeout,
|
||||||
|
idleTimeout,
|
||||||
|
readIdleTimeout,
|
||||||
|
writeIdleTimeout,
|
||||||
|
maxRequestSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"event-executor" -> {
|
||||||
|
val useVirtualThread = root.renderAttribute("use-virtual-threads")
|
||||||
|
?.let(String::toBoolean) ?: true
|
||||||
|
eventExecutor = Configuration.EventExecutor(useVirtualThread)
|
||||||
|
}
|
||||||
"tls" -> {
|
"tls" -> {
|
||||||
val verifyClients = child.renderAttribute("verify-clients")
|
val verifyClients = child.renderAttribute("verify-clients")
|
||||||
?.let(String::toBoolean) ?: false
|
?.let(String::toBoolean) ?: false
|
||||||
@@ -140,18 +170,18 @@ object Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Configuration(
|
return Configuration.of(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
incomingConnectionsBacklogSize,
|
||||||
serverPath,
|
serverPath,
|
||||||
|
eventExecutor,
|
||||||
|
connection,
|
||||||
users,
|
users,
|
||||||
groups,
|
groups,
|
||||||
cache!!,
|
cache!!,
|
||||||
authentication,
|
authentication,
|
||||||
tls,
|
tls,
|
||||||
useVirtualThread,
|
|
||||||
maxRequestSize,
|
|
||||||
incomingConnectionsBacklogSize
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,10 +14,6 @@ object Serializer {
|
|||||||
it.xmlNamespace to it.xmlSchemaLocation
|
it.xmlNamespace to it.xmlSchemaLocation
|
||||||
}.toMap()
|
}.toMap()
|
||||||
return Xml.of(GBCS.GBCS_NAMESPACE_URI, GBCS.GBCS_PREFIX + ":server") {
|
return Xml.of(GBCS.GBCS_NAMESPACE_URI, GBCS.GBCS_PREFIX + ":server") {
|
||||||
attr("use-virtual-threads", conf.isUseVirtualThread.toString())
|
|
||||||
attr("max-request-size", conf.maxRequestSize.toString())
|
|
||||||
attr("incoming-connections-backlog-size", conf.incomingConnectionsBacklogSize.toString())
|
|
||||||
|
|
||||||
// attr("xmlns:xs", GradleBuildCacheServer.XML_SCHEMA_NAMESPACE_URI)
|
// attr("xmlns:xs", GradleBuildCacheServer.XML_SCHEMA_NAMESPACE_URI)
|
||||||
val value = schemaLocations.asSequence().map { (k, v) -> "$k $v" }.joinToString(" ")
|
val value = schemaLocations.asSequence().map { (k, v) -> "$k $v" }.joinToString(" ")
|
||||||
attr("xs:schemaLocation", value , namespaceURI = GBCS.XML_SCHEMA_NAMESPACE_URI)
|
attr("xs:schemaLocation", value , namespaceURI = GBCS.XML_SCHEMA_NAMESPACE_URI)
|
||||||
@@ -30,6 +26,20 @@ object Serializer {
|
|||||||
node("bind") {
|
node("bind") {
|
||||||
attr("host", conf.host)
|
attr("host", conf.host)
|
||||||
attr("port", conf.port.toString())
|
attr("port", conf.port.toString())
|
||||||
|
attr("incoming-connections-backlog-size", conf.incomingConnectionsBacklogSize.toString())
|
||||||
|
}
|
||||||
|
node("connection") {
|
||||||
|
conf.connection.let { connection ->
|
||||||
|
attr("read-timeout", connection.readTimeout.toString())
|
||||||
|
attr("write-timeout", connection.writeTimeout.toString())
|
||||||
|
attr("idle-timeout", connection.idleTimeout.toString())
|
||||||
|
attr("read-idle-timeout", connection.readIdleTimeout.toString())
|
||||||
|
attr("write-idle-timeout", connection.writeIdleTimeout.toString())
|
||||||
|
attr("max-request-size", connection.maxRequestSize.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node("event-executor") {
|
||||||
|
attr("use-virtual-threads", conf.eventExecutor.isUseVirtualThreads.toString())
|
||||||
}
|
}
|
||||||
val cache = conf.cache
|
val cache = conf.cache
|
||||||
val serializer : CacheProvider<Configuration.Cache> =
|
val serializer : CacheProvider<Configuration.Cache> =
|
||||||
|
@@ -0,0 +1,129 @@
|
|||||||
|
package net.woggioni.gbcs.server.handler
|
||||||
|
|
||||||
|
import io.netty.buffer.Unpooled
|
||||||
|
import io.netty.channel.ChannelFutureListener
|
||||||
|
import io.netty.channel.ChannelHandler
|
||||||
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.DefaultFileRegion
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
|
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
||||||
|
import io.netty.handler.codec.http.DefaultHttpResponse
|
||||||
|
import io.netty.handler.codec.http.FullHttpRequest
|
||||||
|
import io.netty.handler.codec.http.HttpHeaderNames
|
||||||
|
import io.netty.handler.codec.http.HttpHeaderValues
|
||||||
|
import io.netty.handler.codec.http.HttpMethod
|
||||||
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
|
import io.netty.handler.codec.http.HttpUtil
|
||||||
|
import io.netty.handler.codec.http.LastHttpContent
|
||||||
|
import io.netty.handler.stream.ChunkedNioFile
|
||||||
|
import io.netty.handler.stream.ChunkedNioStream
|
||||||
|
import net.woggioni.gbcs.api.Cache
|
||||||
|
import net.woggioni.gbcs.common.contextLogger
|
||||||
|
import net.woggioni.gbcs.server.debug
|
||||||
|
import net.woggioni.gbcs.server.warn
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
@ChannelHandler.Sharable
|
||||||
|
class ServerHandler(private val cache: Cache, private val serverPrefix: Path) :
|
||||||
|
SimpleChannelInboundHandler<FullHttpRequest>() {
|
||||||
|
|
||||||
|
private val log = contextLogger()
|
||||||
|
|
||||||
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: FullHttpRequest) {
|
||||||
|
val keepAlive: Boolean = HttpUtil.isKeepAlive(msg)
|
||||||
|
val method = msg.method()
|
||||||
|
if (method === HttpMethod.GET) {
|
||||||
|
val path = Path.of(msg.uri())
|
||||||
|
val prefix = path.parent
|
||||||
|
val key = path.fileName.toString()
|
||||||
|
if (serverPrefix == prefix) {
|
||||||
|
cache.get(key)?.let { channel ->
|
||||||
|
log.debug(ctx) {
|
||||||
|
"Cache hit for key '$key'"
|
||||||
|
}
|
||||||
|
val response = DefaultHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK)
|
||||||
|
response.headers()[HttpHeaderNames.CONTENT_TYPE] = HttpHeaderValues.APPLICATION_OCTET_STREAM
|
||||||
|
if (!keepAlive) {
|
||||||
|
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE)
|
||||||
|
response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.IDENTITY)
|
||||||
|
} else {
|
||||||
|
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE)
|
||||||
|
response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED)
|
||||||
|
}
|
||||||
|
ctx.write(response)
|
||||||
|
when (channel) {
|
||||||
|
is FileChannel -> {
|
||||||
|
if (keepAlive) {
|
||||||
|
ctx.write(ChunkedNioFile(channel))
|
||||||
|
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT)
|
||||||
|
} else {
|
||||||
|
ctx.writeAndFlush(DefaultFileRegion(channel, 0, channel.size()))
|
||||||
|
.addListener(ChannelFutureListener.CLOSE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
ctx.write(ChunkedNioStream(channel))
|
||||||
|
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: let {
|
||||||
|
log.debug(ctx) {
|
||||||
|
"Cache miss for key '$key'"
|
||||||
|
}
|
||||||
|
val response = DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.NOT_FOUND)
|
||||||
|
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
||||||
|
ctx.writeAndFlush(response)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn(ctx) {
|
||||||
|
"Got request for unhandled path '${msg.uri()}'"
|
||||||
|
}
|
||||||
|
val response = DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.BAD_REQUEST)
|
||||||
|
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
||||||
|
ctx.writeAndFlush(response)
|
||||||
|
}
|
||||||
|
} else if (method === HttpMethod.PUT) {
|
||||||
|
val path = Path.of(msg.uri())
|
||||||
|
val prefix = path.parent
|
||||||
|
val key = path.fileName.toString()
|
||||||
|
|
||||||
|
if (serverPrefix == prefix) {
|
||||||
|
log.debug(ctx) {
|
||||||
|
"Added value for key '$key' to build cache"
|
||||||
|
}
|
||||||
|
val bodyBytes = msg.content().run {
|
||||||
|
if (isDirect) {
|
||||||
|
ByteArray(readableBytes()).also {
|
||||||
|
readBytes(it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
array()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache.put(key, bodyBytes)
|
||||||
|
val response = DefaultFullHttpResponse(
|
||||||
|
msg.protocolVersion(), HttpResponseStatus.CREATED,
|
||||||
|
Unpooled.copiedBuffer(key.toByteArray())
|
||||||
|
)
|
||||||
|
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = response.content().readableBytes()
|
||||||
|
ctx.writeAndFlush(response)
|
||||||
|
} else {
|
||||||
|
log.warn(ctx) {
|
||||||
|
"Got request for unhandled path '${msg.uri()}'"
|
||||||
|
}
|
||||||
|
val response = DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.BAD_REQUEST)
|
||||||
|
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
|
||||||
|
ctx.writeAndFlush(response)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn(ctx) {
|
||||||
|
"Got request with unhandled method '${msg.method().name()}'"
|
||||||
|
}
|
||||||
|
val response = DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.BAD_REQUEST)
|
||||||
|
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
|
||||||
|
ctx.writeAndFlush(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -8,6 +8,8 @@
|
|||||||
<xs:complexType name="serverType">
|
<xs:complexType name="serverType">
|
||||||
<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="connection" type="gbcs:connectionType" minOccurs="0" maxOccurs="1"/>
|
||||||
|
<xs:element name="event-executor" type="gbcs:eventExecutorType" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="cache" type="gbcs:cacheType" maxOccurs="1"/>
|
<xs:element name="cache" type="gbcs:cacheType" maxOccurs="1"/>
|
||||||
<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">
|
||||||
@@ -23,14 +25,25 @@
|
|||||||
<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="use-virtual-threads" type="xs:boolean" use="optional" default="true"/>
|
|
||||||
<xs:attribute name="max-request-size" type="xs:unsignedInt" use="optional" default="67108864"/>
|
|
||||||
<xs:attribute name="incoming-connections-backlog-size" type="xs:unsignedInt" use="optional" default="1024"/>
|
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="bindType">
|
<xs:complexType name="bindType">
|
||||||
<xs:attribute name="host" type="xs:token" use="required"/>
|
<xs:attribute name="host" type="xs:token" use="required"/>
|
||||||
<xs:attribute name="port" type="xs:unsignedShort" use="required"/>
|
<xs:attribute name="port" type="xs:unsignedShort" use="required"/>
|
||||||
|
<xs:attribute name="incoming-connections-backlog-size" type="xs:unsignedInt" use="optional" default="1024"/>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="connectionType">
|
||||||
|
<xs:attribute name="read-timeout" type="xs:duration" use="optional" default="PT10S"/>
|
||||||
|
<xs:attribute name="write-timeout" type="xs:duration" use="optional" default="PT10S"/>
|
||||||
|
<xs:attribute name="idle-timeout" type="xs:duration" use="optional" default="PT30S"/>
|
||||||
|
<xs:attribute name="read-idle-timeout" type="xs:duration" use="optional" default="PT60S"/>
|
||||||
|
<xs:attribute name="write-idle-timeout" type="xs:duration" use="optional" default="PT60S"/>
|
||||||
|
<xs:attribute name="max-request-size" type="xs:unsignedInt" use="optional" default="67108864"/>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="eventExecutorType">
|
||||||
|
<xs:attribute name="use-virtual-threads" type="xs:boolean" use="optional" default="true"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="cacheType" abstract="true"/>
|
<xs:complexType name="cacheType" abstract="true"/>
|
||||||
|
@@ -11,6 +11,7 @@ import java.net.http.HttpRequest
|
|||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -30,10 +31,20 @@ abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
|||||||
|
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
this.cacheDir = testDir.resolve("cache")
|
this.cacheDir = testDir.resolve("cache")
|
||||||
cfg = Configuration(
|
cfg = Configuration.of(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
NetworkUtils.getFreePort(),
|
NetworkUtils.getFreePort(),
|
||||||
|
50,
|
||||||
serverPath,
|
serverPath,
|
||||||
|
Configuration.EventExecutor(false),
|
||||||
|
Configuration.Connection(
|
||||||
|
Duration.of(10, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(10, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(60, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
|
0x1000
|
||||||
|
),
|
||||||
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(),
|
||||||
FileSystemCacheConfiguration(this.cacheDir,
|
FileSystemCacheConfiguration(this.cacheDir,
|
||||||
@@ -44,9 +55,6 @@ abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
|||||||
),
|
),
|
||||||
Configuration.BasicAuthentication(),
|
Configuration.BasicAuthentication(),
|
||||||
null,
|
null,
|
||||||
true,
|
|
||||||
0x10000,
|
|
||||||
100
|
|
||||||
)
|
)
|
||||||
Xml.write(Serializer.serialize(cfg), System.out)
|
Xml.write(Serializer.serialize(cfg), System.out)
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ import java.nio.file.Path
|
|||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.KeyStore.PasswordProtection
|
import java.security.KeyStore.PasswordProtection
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
import javax.net.ssl.KeyManagerFactory
|
import javax.net.ssl.KeyManagerFactory
|
||||||
@@ -138,7 +139,17 @@ abstract class AbstractTlsServerTest : AbstractServerTest() {
|
|||||||
cfg = Configuration(
|
cfg = Configuration(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
NetworkUtils.getFreePort(),
|
NetworkUtils.getFreePort(),
|
||||||
|
100,
|
||||||
serverPath,
|
serverPath,
|
||||||
|
Configuration.EventExecutor(false),
|
||||||
|
Configuration.Connection(
|
||||||
|
Duration.of(10, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(10, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(60, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
|
0x1000
|
||||||
|
),
|
||||||
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(),
|
||||||
FileSystemCacheConfiguration(this.cacheDir,
|
FileSystemCacheConfiguration(this.cacheDir,
|
||||||
@@ -156,9 +167,6 @@ abstract class AbstractTlsServerTest : AbstractServerTest() {
|
|||||||
Configuration.TrustStore(this.trustStoreFile, null, false),
|
Configuration.TrustStore(this.trustStoreFile, null, false),
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
false,
|
|
||||||
0x10000,
|
|
||||||
100
|
|
||||||
)
|
)
|
||||||
Xml.write(Serializer.serialize(cfg), System.out)
|
Xml.write(Serializer.serialize(cfg), System.out)
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import java.net.http.HttpRequest
|
|||||||
import java.net.http.HttpResponse
|
import java.net.http.HttpResponse
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -33,7 +34,17 @@ class NoAuthServerTest : AbstractServerTest() {
|
|||||||
cfg = Configuration(
|
cfg = Configuration(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
NetworkUtils.getFreePort(),
|
NetworkUtils.getFreePort(),
|
||||||
|
100,
|
||||||
serverPath,
|
serverPath,
|
||||||
|
Configuration.EventExecutor(false),
|
||||||
|
Configuration.Connection(
|
||||||
|
Duration.of(10, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(10, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(60, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
|
0x1000
|
||||||
|
),
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
FileSystemCacheConfiguration(
|
FileSystemCacheConfiguration(
|
||||||
@@ -45,9 +56,6 @@ class NoAuthServerTest : AbstractServerTest() {
|
|||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
true,
|
|
||||||
0x10000,
|
|
||||||
100
|
|
||||||
)
|
)
|
||||||
Xml.write(Serializer.serialize(cfg), System.out)
|
Xml.write(Serializer.serialize(cfg), System.out)
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<gbcs:server use-virtual-threads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
<gbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:gbcs="urn:net.woggioni.gbcs.server"
|
xmlns:gbcs="urn:net.woggioni.gbcs.server"
|
||||||
xs:schemaLocation="urn:net.woggioni.gbcs.server jpms://net.woggioni.gbcs.server/net/woggioni/gbcs/server/schema/gbcs.xsd">
|
xs:schemaLocation="urn:net.woggioni.gbcs.server jpms://net.woggioni.gbcs.server/net/woggioni/gbcs/server/schema/gbcs.xsd">
|
||||||
<bind host="127.0.0.1" port="11443"/>
|
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="22"/>
|
||||||
|
<connection
|
||||||
|
write-timeout="PT25M"
|
||||||
|
read-timeout="PT20M"
|
||||||
|
read-idle-timeout="PT10M"
|
||||||
|
write-idle-timeout="PT11M"
|
||||||
|
idle-timeout="PT30M"
|
||||||
|
max-request-size="101325"/>
|
||||||
|
<event-executor use-virtual-threads="false"/>
|
||||||
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
|
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
|
||||||
<authentication>
|
<authentication>
|
||||||
<none/>
|
<none/>
|
||||||
|
@@ -1,9 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<gbcs:server use-virtual-threads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
<gbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:gbcs="urn:net.woggioni.gbcs.server"
|
xmlns:gbcs="urn:net.woggioni.gbcs.server"
|
||||||
xmlns:gbcs-memcached="urn:net.woggioni.gbcs.server.memcached"
|
xmlns:gbcs-memcached="urn:net.woggioni.gbcs.server.memcached"
|
||||||
xs:schemaLocation="urn:net.woggioni.gbcs.server.memcached jpms://net.woggioni.gbcs.server.memcached/net/woggioni/gbcs/server/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs.server jpms://net.woggioni.gbcs.server/net/woggioni/gbcs/server/schema/gbcs.xsd">
|
xs:schemaLocation="urn:net.woggioni.gbcs.server.memcached jpms://net.woggioni.gbcs.server.memcached/net/woggioni/gbcs/server/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs.server jpms://net.woggioni.gbcs.server/net/woggioni/gbcs/server/schema/gbcs.xsd">
|
||||||
<bind host="127.0.0.1" port="11443" />
|
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="50"/>
|
||||||
|
<connection
|
||||||
|
write-timeout="PT25M"
|
||||||
|
read-timeout="PT20M"
|
||||||
|
read-idle-timeout="PT10M"
|
||||||
|
write-idle-timeout="PT11M"
|
||||||
|
idle-timeout="PT30M"
|
||||||
|
max-request-size="101325"/>
|
||||||
|
<event-executor use-virtual-threads="false"/>
|
||||||
<cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="101325" digest="SHA-256">
|
<cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="101325" digest="SHA-256">
|
||||||
<server host="127.0.0.1" port="11211"/>
|
<server host="127.0.0.1" port="11211"/>
|
||||||
</cache>
|
</cache>
|
||||||
|
@@ -1,8 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<gbcs:server use-virtual-threads="false" max-request-size="4096" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
<gbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:gbcs="urn:net.woggioni.gbcs.server"
|
xmlns:gbcs="urn:net.woggioni.gbcs.server"
|
||||||
xs:schemaLocation="urn:net.woggioni.gbcs.server jpms://net.woggioni.gbcs.server/net/woggioni/gbcs/server/schema/gbcs.xsd">
|
xs:schemaLocation="urn:net.woggioni.gbcs.server jpms://net.woggioni.gbcs.server/net/woggioni/gbcs/server/schema/gbcs.xsd">
|
||||||
<bind host="127.0.0.1" port="11443"/>
|
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="180"/>
|
||||||
|
<connection
|
||||||
|
write-timeout="PT25M"
|
||||||
|
read-timeout="PT20M"
|
||||||
|
read-idle-timeout="PT10M"
|
||||||
|
write-idle-timeout="PT11M"
|
||||||
|
idle-timeout="PT30M"
|
||||||
|
max-request-size="4096"/>
|
||||||
|
<event-executor use-virtual-threads="false"/>
|
||||||
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
|
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
|
||||||
<authorization>
|
<authorization>
|
||||||
<users>
|
<users>
|
||||||
|
Reference in New Issue
Block a user