Compare commits
3 Commits
5545f618f9
...
59a12d6218
Author | SHA1 | Date | |
---|---|---|---|
59a12d6218
|
|||
fc298de548
|
|||
8b639fc0b3
|
@@ -39,7 +39,6 @@ allprojects { subproject ->
|
|||||||
modularity.inferModulePath = true
|
modularity.inferModulePath = true
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
vendor = JvmVendorSpec.GRAAL_VM
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@ Configures connection handling parameters.
|
|||||||
- `read-idle-timeout` (optional, default: PT60S): Connection timeout when no reads
|
- `read-idle-timeout` (optional, default: PT60S): Connection timeout when no reads
|
||||||
- `write-idle-timeout` (optional, default: PT60S): Connection timeout when no writes
|
- `write-idle-timeout` (optional, default: PT60S): Connection timeout when no writes
|
||||||
- `max-request-size` (optional, default: 0x4000000): Maximum allowed request body size
|
- `max-request-size` (optional, default: 0x4000000): Maximum allowed request body size
|
||||||
|
- `chunk-size` (default: 0x10000): Maximum socket write size
|
||||||
|
|
||||||
#### `<event-executor>`
|
#### `<event-executor>`
|
||||||
Configures event execution settings.
|
Configures event execution settings.
|
||||||
@@ -44,7 +45,6 @@ A simple storage backend that uses an hash map to store data in memory
|
|||||||
- `digest` (default: MD5): Key hashing algorithm
|
- `digest` (default: MD5): Key hashing algorithm
|
||||||
- `enable-compression` (default: true): Enable deflate compression
|
- `enable-compression` (default: true): Enable deflate compression
|
||||||
- `compression-level` (default: -1): Compression level (-1 to 9)
|
- `compression-level` (default: -1): Compression level (-1 to 9)
|
||||||
- `chunk-size` (default: 0x10000): Maximum socket write size
|
|
||||||
|
|
||||||
##### FileSystem Cache
|
##### FileSystem Cache
|
||||||
|
|
||||||
@@ -56,7 +56,6 @@ A storage backend that stores data in a folder on the disk
|
|||||||
- `digest` (default: MD5): Key hashing algorithm
|
- `digest` (default: MD5): Key hashing algorithm
|
||||||
- `enable-compression` (default: true): Enable deflate compression
|
- `enable-compression` (default: true): Enable deflate compression
|
||||||
- `compression-level` (default: -1): Compression level
|
- `compression-level` (default: -1): Compression level
|
||||||
- `chunk-size` (default: 0x10000): Maximum in-memory cache value size
|
|
||||||
|
|
||||||
#### `<authorization>`
|
#### `<authorization>`
|
||||||
Configures user and group-based access control.
|
Configures user and group-based access control.
|
||||||
@@ -134,8 +133,7 @@ Configures TLS encryption.
|
|||||||
idle-timeout="PT10S"
|
idle-timeout="PT10S"
|
||||||
read-idle-timeout="PT20S"
|
read-idle-timeout="PT20S"
|
||||||
write-idle-timeout="PT20S"
|
write-idle-timeout="PT20S"
|
||||||
read-timeout="PT5S"
|
chunk-size="0x1000"/>
|
||||||
write-timeout="PT5S"/>
|
|
||||||
<event-executor use-virtual-threads="true"/>
|
<event-executor use-virtual-threads="true"/>
|
||||||
|
|
||||||
<cache xs:type="rbcs:inMemoryCacheType" max-age="P7D" enable-compression="false" max-size="0x10000000" />
|
<cache xs:type="rbcs:inMemoryCacheType" max-age="P7D" enable-compression="false" max-size="0x10000000" />
|
||||||
@@ -147,7 +145,7 @@ Configures TLS encryption.
|
|||||||
<!-- uncomment this to use memcache as the storage backend, also make sure you have
|
<!-- uncomment this to use memcache as the storage backend, also make sure you have
|
||||||
the memcache plugin installed in the `plugins` directory if you are using running
|
the memcache plugin installed in the `plugins` directory if you are using running
|
||||||
the jar version of RBCS
|
the jar version of RBCS
|
||||||
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" chunk-size="0x1000" digest="MD5">
|
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" digest="MD5">
|
||||||
<server host="127.0.0.1" port="11211" max-connections="256"/>
|
<server host="127.0.0.1" port="11211" max-connections="256"/>
|
||||||
</cache>
|
</cache>
|
||||||
-->
|
-->
|
||||||
|
@@ -8,6 +8,7 @@ import io.netty.channel.socket.SocketChannel;
|
|||||||
|
|
||||||
public interface CacheHandlerFactory extends AsyncCloseable {
|
public interface CacheHandlerFactory extends AsyncCloseable {
|
||||||
ChannelHandler newHandler(
|
ChannelHandler newHandler(
|
||||||
|
Configuration configuration,
|
||||||
EventLoopGroup eventLoopGroup,
|
EventLoopGroup eventLoopGroup,
|
||||||
ChannelFactory<SocketChannel> socketChannelFactory,
|
ChannelFactory<SocketChannel> socketChannelFactory,
|
||||||
ChannelFactory<DatagramChannel> datagramChannelFactory
|
ChannelFactory<DatagramChannel> datagramChannelFactory
|
||||||
|
@@ -39,6 +39,7 @@ public class Configuration {
|
|||||||
Duration readIdleTimeout;
|
Duration readIdleTimeout;
|
||||||
Duration writeIdleTimeout;
|
Duration writeIdleTimeout;
|
||||||
int maxRequestSize;
|
int maxRequestSize;
|
||||||
|
int chunkSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
|
@@ -91,6 +91,10 @@ Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named(EnvelopePlugin.E
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
|
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
vendor = JvmVendorSpec.GRAAL_VM
|
||||||
|
}
|
||||||
mainClass = "net.woggioni.rbcs.cli.graal.GraalNativeImageConfiguration"
|
mainClass = "net.woggioni.rbcs.cli.graal.GraalNativeImageConfiguration"
|
||||||
classpath = project.files(
|
classpath = project.files(
|
||||||
configurations.configureNativeImageRuntimeClasspath,
|
configurations.configureNativeImageRuntimeClasspath,
|
||||||
|
@@ -4,9 +4,7 @@ import io.netty.bootstrap.Bootstrap
|
|||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.buffer.Unpooled
|
import io.netty.buffer.Unpooled
|
||||||
import io.netty.channel.Channel
|
import io.netty.channel.Channel
|
||||||
import io.netty.channel.ChannelHandler
|
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter
|
|
||||||
import io.netty.channel.ChannelOption
|
import io.netty.channel.ChannelOption
|
||||||
import io.netty.channel.ChannelPipeline
|
import io.netty.channel.ChannelPipeline
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
@@ -55,7 +53,7 @@ import kotlin.random.Random
|
|||||||
import io.netty.util.concurrent.Future as NettyFuture
|
import io.netty.util.concurrent.Future as NettyFuture
|
||||||
|
|
||||||
class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoCloseable {
|
class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoCloseable {
|
||||||
companion object{
|
companion object {
|
||||||
private val log = createLogger<RemoteBuildCacheClient>()
|
private val log = createLogger<RemoteBuildCacheClient>()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +71,7 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
*tlsClientAuthenticationCredentials.certificateChain
|
*tlsClientAuthenticationCredentials.certificateChain
|
||||||
)
|
)
|
||||||
profile.tlsTruststore?.let { trustStore ->
|
profile.tlsTruststore?.let { trustStore ->
|
||||||
if(!trustStore.verifyServerCertificate) {
|
if (!trustStore.verifyServerCertificate) {
|
||||||
trustManager(object : X509TrustManager {
|
trustManager(object : X509TrustManager {
|
||||||
override fun checkClientTrusted(certChain: Array<out X509Certificate>, p1: String?) {
|
override fun checkClientTrusted(certChain: Array<out X509Certificate>, p1: String?) {
|
||||||
}
|
}
|
||||||
@@ -176,7 +174,7 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
|
|
||||||
// HTTP handlers
|
// HTTP handlers
|
||||||
pipeline.addLast("codec", HttpClientCodec())
|
pipeline.addLast("codec", HttpClientCodec())
|
||||||
if(profile.compressionEnabled) {
|
if (profile.compressionEnabled) {
|
||||||
pipeline.addLast("decompressor", HttpContentDecompressor())
|
pipeline.addLast("decompressor", HttpContentDecompressor())
|
||||||
}
|
}
|
||||||
pipeline.addLast("aggregator", HttpObjectAggregator(134217728))
|
pipeline.addLast("aggregator", HttpObjectAggregator(134217728))
|
||||||
@@ -297,47 +295,28 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
// Custom handler for processing responses
|
// Custom handler for processing responses
|
||||||
|
|
||||||
pool.acquire().addListener(object : GenericFutureListener<NettyFuture<Channel>> {
|
pool.acquire().addListener(object : GenericFutureListener<NettyFuture<Channel>> {
|
||||||
private val handlers = mutableListOf<ChannelHandler>()
|
|
||||||
|
|
||||||
fun cleanup(channel: Channel, pipeline: ChannelPipeline) {
|
|
||||||
handlers.forEach(pipeline::remove)
|
|
||||||
pool.release(channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun operationComplete(channelFuture: Future<Channel>) {
|
override fun operationComplete(channelFuture: Future<Channel>) {
|
||||||
if (channelFuture.isSuccess) {
|
if (channelFuture.isSuccess) {
|
||||||
val channel = channelFuture.now
|
val channel = channelFuture.now
|
||||||
val pipeline = channel.pipeline()
|
val pipeline = channel.pipeline()
|
||||||
val timeoutHandler = object : ChannelInboundHandlerAdapter() {
|
|
||||||
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
|
||||||
if (evt is IdleStateEvent) {
|
|
||||||
val te = when (evt.state()) {
|
|
||||||
IdleState.READER_IDLE -> TimeoutException(
|
|
||||||
"Read timeout",
|
|
||||||
)
|
|
||||||
|
|
||||||
IdleState.WRITER_IDLE -> TimeoutException("Write timeout")
|
|
||||||
|
|
||||||
IdleState.ALL_IDLE -> TimeoutException("Idle timeout")
|
|
||||||
null -> throw IllegalStateException("This should never happen")
|
|
||||||
}
|
|
||||||
responseFuture.completeExceptionally(te)
|
|
||||||
ctx.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val closeListener = GenericFutureListener<Future<Void>> {
|
val closeListener = GenericFutureListener<Future<Void>> {
|
||||||
responseFuture.completeExceptionally(IOException("The remote server closed the connection"))
|
responseFuture.completeExceptionally(IOException("The remote server closed the connection"))
|
||||||
pool.release(channel)
|
|
||||||
}
|
}
|
||||||
|
channel.closeFuture().addListener(closeListener)
|
||||||
|
|
||||||
val responseHandler = object : SimpleChannelInboundHandler<FullHttpResponse>() {
|
val responseHandler = object : SimpleChannelInboundHandler<FullHttpResponse>() {
|
||||||
|
|
||||||
|
override fun handlerAdded(ctx: ChannelHandlerContext) {
|
||||||
|
channel.closeFuture().removeListener(closeListener)
|
||||||
|
}
|
||||||
|
|
||||||
override fun channelRead0(
|
override fun channelRead0(
|
||||||
ctx: ChannelHandlerContext,
|
ctx: ChannelHandlerContext,
|
||||||
response: FullHttpResponse
|
response: FullHttpResponse
|
||||||
) {
|
) {
|
||||||
channel.closeFuture().removeListener(closeListener)
|
pipeline.remove(this)
|
||||||
cleanup(channel, pipeline)
|
|
||||||
responseFuture.complete(response)
|
responseFuture.complete(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,16 +331,33 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun channelInactive(ctx: ChannelHandlerContext) {
|
override fun channelInactive(ctx: ChannelHandlerContext) {
|
||||||
pool.release(channel)
|
|
||||||
responseFuture.completeExceptionally(IOException("The remote server closed the connection"))
|
responseFuture.completeExceptionally(IOException("The remote server closed the connection"))
|
||||||
super.channelInactive(ctx)
|
super.channelInactive(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
||||||
|
if (evt is IdleStateEvent) {
|
||||||
|
val te = when (evt.state()) {
|
||||||
|
IdleState.READER_IDLE -> TimeoutException(
|
||||||
|
"Read timeout",
|
||||||
|
)
|
||||||
|
|
||||||
|
IdleState.WRITER_IDLE -> TimeoutException("Write timeout")
|
||||||
|
|
||||||
|
IdleState.ALL_IDLE -> TimeoutException("Idle timeout")
|
||||||
|
null -> throw IllegalStateException("This should never happen")
|
||||||
|
}
|
||||||
|
responseFuture.completeExceptionally(te)
|
||||||
|
super.userEventTriggered(ctx, evt)
|
||||||
|
if (this === pipeline.last()) {
|
||||||
|
ctx.close()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.userEventTriggered(ctx, evt)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (handler in arrayOf(timeoutHandler, responseHandler)) {
|
pipeline.addLast(responseHandler)
|
||||||
handlers.add(handler)
|
|
||||||
}
|
|
||||||
pipeline.addLast(timeoutHandler, responseHandler)
|
|
||||||
channel.closeFuture().addListener(closeListener)
|
|
||||||
|
|
||||||
|
|
||||||
// Prepare the HTTP request
|
// Prepare the HTTP request
|
||||||
@@ -373,13 +369,14 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
uri.rawPath,
|
uri.rawPath,
|
||||||
content ?: Unpooled.buffer(0)
|
content ?: Unpooled.buffer(0)
|
||||||
).apply {
|
).apply {
|
||||||
|
// Set headers
|
||||||
headers().apply {
|
headers().apply {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes())
|
set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes())
|
||||||
}
|
}
|
||||||
set(HttpHeaderNames.HOST, profile.serverURI.host)
|
set(HttpHeaderNames.HOST, profile.serverURI.host)
|
||||||
set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE)
|
set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE)
|
||||||
if(profile.compressionEnabled) {
|
if (profile.compressionEnabled) {
|
||||||
set(
|
set(
|
||||||
HttpHeaderNames.ACCEPT_ENCODING,
|
HttpHeaderNames.ACCEPT_ENCODING,
|
||||||
HttpHeaderValues.GZIP.toString() + "," + HttpHeaderValues.DEFLATE.toString()
|
HttpHeaderValues.GZIP.toString() + "," + HttpHeaderValues.DEFLATE.toString()
|
||||||
@@ -398,9 +395,15 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set headers
|
|
||||||
// Send the request
|
// Send the request
|
||||||
channel.writeAndFlush(request)
|
channel.writeAndFlush(request).addListener {
|
||||||
|
if(!it.isSuccess) {
|
||||||
|
val ex = it.cause()
|
||||||
|
log.warn(ex.message, ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.release(channel)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
responseFuture.completeExceptionally(channelFuture.cause())
|
responseFuture.completeExceptionally(channelFuture.cause())
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ The plugins currently supports the following configuration attributes:
|
|||||||
- `digest`: digest algorithm to use on the key before submission
|
- `digest`: digest algorithm to use on the key before submission
|
||||||
to memcache (optional, no digest is applied if omitted)
|
to memcache (optional, no digest is applied if omitted)
|
||||||
- `compression`: compression algorithm to apply to cache values before,
|
- `compression`: compression algorithm to apply to cache values before,
|
||||||
currently only `deflate` is supported (optionla, if omitted compression is disabled)
|
currently only `deflate` is supported (optional, if omitted compression is disabled)
|
||||||
- `compression-level`: compression level to use, deflate supports compression levels from 1 to 9,
|
- `compression-level`: compression level to use, deflate supports compression levels from 1 to 9,
|
||||||
where 1 is for fast compression at the expense of speed (optional, 6 is used if omitted)
|
where 1 is for fast compression at the expense of speed (optional, 6 is used if omitted)
|
||||||
```xml
|
```xml
|
||||||
@@ -37,8 +37,7 @@ The plugins currently supports the following configuration attributes:
|
|||||||
max-age="P7D"
|
max-age="P7D"
|
||||||
digest="SHA-256"
|
digest="SHA-256"
|
||||||
compression-mode="deflate"
|
compression-mode="deflate"
|
||||||
compression-level="6"
|
compression-level="6">
|
||||||
chunk-size="0x10000">
|
|
||||||
<server host="127.0.0.1" port="11211" max-connections="256"/>
|
<server host="127.0.0.1" port="11211" max-connections="256"/>
|
||||||
<server host="127.0.0.1" port="11212" max-connections="256"/>
|
<server host="127.0.0.1" port="11212" max-connections="256"/>
|
||||||
</cache>
|
</cache>
|
||||||
|
@@ -23,7 +23,6 @@ data class MemcacheCacheConfiguration(
|
|||||||
val digestAlgorithm: String? = null,
|
val digestAlgorithm: String? = null,
|
||||||
val compressionMode: CompressionMode? = null,
|
val compressionMode: CompressionMode? = null,
|
||||||
val compressionLevel: Int,
|
val compressionLevel: Int,
|
||||||
val chunkSize: Int
|
|
||||||
) : Configuration.Cache {
|
) : Configuration.Cache {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -48,14 +47,15 @@ data class MemcacheCacheConfiguration(
|
|||||||
private val connectionPoolMap = ConcurrentHashMap<HostAndPort, FixedChannelPool>()
|
private val connectionPoolMap = ConcurrentHashMap<HostAndPort, FixedChannelPool>()
|
||||||
|
|
||||||
override fun newHandler(
|
override fun newHandler(
|
||||||
|
cfg : Configuration,
|
||||||
eventLoop: EventLoopGroup,
|
eventLoop: EventLoopGroup,
|
||||||
socketChannelFactory: ChannelFactory<SocketChannel>,
|
socketChannelFactory: ChannelFactory<SocketChannel>,
|
||||||
datagramChannelFactory: ChannelFactory<DatagramChannel>
|
datagramChannelFactory: ChannelFactory<DatagramChannel>,
|
||||||
): ChannelHandler {
|
): ChannelHandler {
|
||||||
return MemcacheCacheHandler(
|
return MemcacheCacheHandler(
|
||||||
MemcacheClient(
|
MemcacheClient(
|
||||||
this@MemcacheCacheConfiguration.servers,
|
this@MemcacheCacheConfiguration.servers,
|
||||||
chunkSize,
|
cfg.connection.chunkSize,
|
||||||
eventLoop,
|
eventLoop,
|
||||||
socketChannelFactory,
|
socketChannelFactory,
|
||||||
connectionPoolMap
|
connectionPoolMap
|
||||||
@@ -63,7 +63,7 @@ data class MemcacheCacheConfiguration(
|
|||||||
digestAlgorithm,
|
digestAlgorithm,
|
||||||
compressionMode != null,
|
compressionMode != null,
|
||||||
compressionLevel,
|
compressionLevel,
|
||||||
chunkSize,
|
cfg.connection.chunkSize,
|
||||||
maxAge
|
maxAge
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -28,9 +28,6 @@ class MemcacheCacheProvider : CacheProvider<MemcacheCacheConfiguration> {
|
|||||||
val maxAge = el.renderAttribute("max-age")
|
val maxAge = el.renderAttribute("max-age")
|
||||||
?.let(Duration::parse)
|
?.let(Duration::parse)
|
||||||
?: Duration.ofDays(1)
|
?: Duration.ofDays(1)
|
||||||
val chunkSize = el.renderAttribute("chunk-size")
|
|
||||||
?.let(Integer::decode)
|
|
||||||
?: 0x10000
|
|
||||||
val compressionLevel = el.renderAttribute("compression-level")
|
val compressionLevel = el.renderAttribute("compression-level")
|
||||||
?.let(Integer::decode)
|
?.let(Integer::decode)
|
||||||
?: -1
|
?: -1
|
||||||
@@ -63,8 +60,7 @@ class MemcacheCacheProvider : CacheProvider<MemcacheCacheConfiguration> {
|
|||||||
maxAge,
|
maxAge,
|
||||||
digestAlgorithm,
|
digestAlgorithm,
|
||||||
compressionMode,
|
compressionMode,
|
||||||
compressionLevel,
|
compressionLevel
|
||||||
chunkSize
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +80,6 @@ class MemcacheCacheProvider : CacheProvider<MemcacheCacheConfiguration> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
attr("max-age", maxAge.toString())
|
attr("max-age", maxAge.toString())
|
||||||
attr("chunk-size", chunkSize.toString())
|
|
||||||
digestAlgorithm?.let { digestAlgorithm ->
|
digestAlgorithm?.let { digestAlgorithm ->
|
||||||
attr("digest", digestAlgorithm)
|
attr("digest", digestAlgorithm)
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ module net.woggioni.rbcs.server {
|
|||||||
opens net.woggioni.rbcs.server;
|
opens net.woggioni.rbcs.server;
|
||||||
opens net.woggioni.rbcs.server.schema;
|
opens net.woggioni.rbcs.server.schema;
|
||||||
|
|
||||||
|
|
||||||
uses CacheProvider;
|
uses CacheProvider;
|
||||||
provides CacheProvider with FileSystemCacheProvider, InMemoryCacheProvider;
|
provides CacheProvider with FileSystemCacheProvider, InMemoryCacheProvider;
|
||||||
}
|
}
|
@@ -21,6 +21,7 @@ import io.netty.channel.socket.nio.NioSocketChannel
|
|||||||
import io.netty.handler.codec.compression.CompressionOptions
|
import io.netty.handler.codec.compression.CompressionOptions
|
||||||
import io.netty.handler.codec.http.DefaultHttpContent
|
import io.netty.handler.codec.http.DefaultHttpContent
|
||||||
import io.netty.handler.codec.http.HttpContentCompressor
|
import io.netty.handler.codec.http.HttpContentCompressor
|
||||||
|
import io.netty.handler.codec.http.HttpDecoderConfig
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames
|
import io.netty.handler.codec.http.HttpHeaderNames
|
||||||
import io.netty.handler.codec.http.HttpRequest
|
import io.netty.handler.codec.http.HttpRequest
|
||||||
import io.netty.handler.codec.http.HttpServerCodec
|
import io.netty.handler.codec.http.HttpServerCodec
|
||||||
@@ -55,7 +56,6 @@ import net.woggioni.rbcs.server.configuration.Serializer
|
|||||||
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
||||||
import net.woggioni.rbcs.server.handler.MaxRequestSizeHandler
|
import net.woggioni.rbcs.server.handler.MaxRequestSizeHandler
|
||||||
import net.woggioni.rbcs.server.handler.ServerHandler
|
import net.woggioni.rbcs.server.handler.ServerHandler
|
||||||
import net.woggioni.rbcs.server.handler.TraceHandler
|
|
||||||
import net.woggioni.rbcs.server.throttling.BucketManager
|
import net.woggioni.rbcs.server.throttling.BucketManager
|
||||||
import net.woggioni.rbcs.server.throttling.ThrottlingHandler
|
import net.woggioni.rbcs.server.throttling.ThrottlingHandler
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@@ -340,7 +340,10 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
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())
|
val httpDecoderConfig = HttpDecoderConfig().apply {
|
||||||
|
maxChunkSize = cfg.connection.chunkSize
|
||||||
|
}
|
||||||
|
pipeline.addLast(HttpServerCodec(httpDecoderConfig))
|
||||||
pipeline.addLast(MaxRequestSizeHandler.NAME, MaxRequestSizeHandler(cfg.connection.maxRequestSize))
|
pipeline.addLast(MaxRequestSizeHandler.NAME, MaxRequestSizeHandler(cfg.connection.maxRequestSize))
|
||||||
pipeline.addLast(HttpChunkContentCompressor(1024))
|
pipeline.addLast(HttpChunkContentCompressor(1024))
|
||||||
pipeline.addLast(ChunkedWriteHandler())
|
pipeline.addLast(ChunkedWriteHandler())
|
||||||
@@ -351,13 +354,12 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
|
|
||||||
val serverHandler = let {
|
val serverHandler = let {
|
||||||
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
|
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
|
||||||
ServerHandler(prefix)
|
ServerHandler(prefix) {
|
||||||
|
cacheHandlerFactory.newHandler(cfg, ch.eventLoop(), channelFactory, datagramChannelFactory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pipeline.addLast(eventExecutorGroup, ServerHandler.NAME, serverHandler)
|
pipeline.addLast(eventExecutorGroup, ServerHandler.NAME, serverHandler)
|
||||||
|
pipeline.addLast(ExceptionHandler.NAME, ExceptionHandler)
|
||||||
pipeline.addLast(cacheHandlerFactory.newHandler(ch.eventLoop(), channelFactory, datagramChannelFactory))
|
|
||||||
pipeline.addLast(TraceHandler)
|
|
||||||
pipeline.addLast(ExceptionHandler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun asyncClose() = cacheHandlerFactory.asyncClose()
|
override fun asyncClose() = cacheHandlerFactory.asyncClose()
|
||||||
|
@@ -17,7 +17,6 @@ data class FileSystemCacheConfiguration(
|
|||||||
val digestAlgorithm : String?,
|
val digestAlgorithm : String?,
|
||||||
val compressionEnabled: Boolean,
|
val compressionEnabled: Boolean,
|
||||||
val compressionLevel: Int,
|
val compressionLevel: Int,
|
||||||
val chunkSize: Int,
|
|
||||||
) : Configuration.Cache {
|
) : Configuration.Cache {
|
||||||
|
|
||||||
override fun materialize() = object : CacheHandlerFactory {
|
override fun materialize() = object : CacheHandlerFactory {
|
||||||
@@ -26,10 +25,11 @@ data class FileSystemCacheConfiguration(
|
|||||||
override fun asyncClose() = cache.asyncClose()
|
override fun asyncClose() = cache.asyncClose()
|
||||||
|
|
||||||
override fun newHandler(
|
override fun newHandler(
|
||||||
|
cfg : Configuration,
|
||||||
eventLoop: EventLoopGroup,
|
eventLoop: EventLoopGroup,
|
||||||
socketChannelFactory: ChannelFactory<SocketChannel>,
|
socketChannelFactory: ChannelFactory<SocketChannel>,
|
||||||
datagramChannelFactory: ChannelFactory<DatagramChannel>
|
datagramChannelFactory: ChannelFactory<DatagramChannel>
|
||||||
) = FileSystemCacheHandler(cache, digestAlgorithm, compressionEnabled, compressionLevel, chunkSize)
|
) = FileSystemCacheHandler(cache, digestAlgorithm, compressionEnabled, compressionLevel, cfg.connection.chunkSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNamespaceURI() = RBCS.RBCS_NAMESPACE_URI
|
override fun getNamespaceURI() = RBCS.RBCS_NAMESPACE_URI
|
||||||
|
@@ -31,9 +31,6 @@ class FileSystemCacheProvider : CacheProvider<FileSystemCacheConfiguration> {
|
|||||||
?.let(String::toInt)
|
?.let(String::toInt)
|
||||||
?: Deflater.DEFAULT_COMPRESSION
|
?: Deflater.DEFAULT_COMPRESSION
|
||||||
val digestAlgorithm = el.renderAttribute("digest")
|
val digestAlgorithm = el.renderAttribute("digest")
|
||||||
val chunkSize = el.renderAttribute("chunk-size")
|
|
||||||
?.let(Integer::decode)
|
|
||||||
?: 0x10000
|
|
||||||
|
|
||||||
return FileSystemCacheConfiguration(
|
return FileSystemCacheConfiguration(
|
||||||
path,
|
path,
|
||||||
@@ -41,7 +38,6 @@ class FileSystemCacheProvider : CacheProvider<FileSystemCacheConfiguration> {
|
|||||||
digestAlgorithm,
|
digestAlgorithm,
|
||||||
enableCompression,
|
enableCompression,
|
||||||
compressionLevel,
|
compressionLevel,
|
||||||
chunkSize
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +59,6 @@ class FileSystemCacheProvider : CacheProvider<FileSystemCacheConfiguration> {
|
|||||||
}?.let {
|
}?.let {
|
||||||
attr("compression-level", it.toString())
|
attr("compression-level", it.toString())
|
||||||
}
|
}
|
||||||
attr("chunk-size", chunkSize.toString())
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,6 @@ data class InMemoryCacheConfiguration(
|
|||||||
val digestAlgorithm : String?,
|
val digestAlgorithm : String?,
|
||||||
val compressionEnabled: Boolean,
|
val compressionEnabled: Boolean,
|
||||||
val compressionLevel: Int,
|
val compressionLevel: Int,
|
||||||
val chunkSize : Int
|
|
||||||
) : Configuration.Cache {
|
) : Configuration.Cache {
|
||||||
override fun materialize() = object : CacheHandlerFactory {
|
override fun materialize() = object : CacheHandlerFactory {
|
||||||
private val cache = InMemoryCache(maxAge, maxSize)
|
private val cache = InMemoryCache(maxAge, maxSize)
|
||||||
@@ -24,6 +23,7 @@ data class InMemoryCacheConfiguration(
|
|||||||
override fun asyncClose() = cache.asyncClose()
|
override fun asyncClose() = cache.asyncClose()
|
||||||
|
|
||||||
override fun newHandler(
|
override fun newHandler(
|
||||||
|
cfg : Configuration,
|
||||||
eventLoop: EventLoopGroup,
|
eventLoop: EventLoopGroup,
|
||||||
socketChannelFactory: ChannelFactory<SocketChannel>,
|
socketChannelFactory: ChannelFactory<SocketChannel>,
|
||||||
datagramChannelFactory: ChannelFactory<DatagramChannel>
|
datagramChannelFactory: ChannelFactory<DatagramChannel>
|
||||||
|
@@ -31,16 +31,12 @@ class InMemoryCacheProvider : CacheProvider<InMemoryCacheConfiguration> {
|
|||||||
?.let(String::toInt)
|
?.let(String::toInt)
|
||||||
?: Deflater.DEFAULT_COMPRESSION
|
?: Deflater.DEFAULT_COMPRESSION
|
||||||
val digestAlgorithm = el.renderAttribute("digest")
|
val digestAlgorithm = el.renderAttribute("digest")
|
||||||
val chunkSize = el.renderAttribute("chunk-size")
|
|
||||||
?.let(Integer::decode)
|
|
||||||
?: 0x10000
|
|
||||||
return InMemoryCacheConfiguration(
|
return InMemoryCacheConfiguration(
|
||||||
maxAge,
|
maxAge,
|
||||||
maxSize,
|
maxSize,
|
||||||
digestAlgorithm,
|
digestAlgorithm,
|
||||||
enableCompression,
|
enableCompression,
|
||||||
compressionLevel,
|
compressionLevel,
|
||||||
chunkSize
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +56,6 @@ class InMemoryCacheProvider : CacheProvider<InMemoryCacheConfiguration> {
|
|||||||
}?.let {
|
}?.let {
|
||||||
attr("compression-level", it.toString())
|
attr("compression-level", it.toString())
|
||||||
}
|
}
|
||||||
attr("chunk-size", chunkSize.toString())
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@@ -27,10 +27,11 @@ object Parser {
|
|||||||
val root = document.documentElement
|
val root = document.documentElement
|
||||||
val anonymousUser = User("", null, emptySet(), null)
|
val anonymousUser = User("", null, emptySet(), null)
|
||||||
var connection: Configuration.Connection = Configuration.Connection(
|
var connection: Configuration.Connection = Configuration.Connection(
|
||||||
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
Duration.of(60, ChronoUnit.SECONDS),
|
Duration.of(60, ChronoUnit.SECONDS),
|
||||||
Duration.of(30, ChronoUnit.SECONDS),
|
Duration.of(60, ChronoUnit.SECONDS),
|
||||||
Duration.of(30, ChronoUnit.SECONDS),
|
0x4000000,
|
||||||
67108864
|
0x10000
|
||||||
)
|
)
|
||||||
var eventExecutor: Configuration.EventExecutor = Configuration.EventExecutor(true)
|
var eventExecutor: Configuration.EventExecutor = Configuration.EventExecutor(true)
|
||||||
var cache: Cache? = null
|
var cache: Cache? = null
|
||||||
@@ -119,11 +120,14 @@ object Parser {
|
|||||||
?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS)
|
?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS)
|
||||||
val maxRequestSize = child.renderAttribute("max-request-size")
|
val maxRequestSize = child.renderAttribute("max-request-size")
|
||||||
?.let(Integer::decode) ?: 0x4000000
|
?.let(Integer::decode) ?: 0x4000000
|
||||||
|
val chunkSize = child.renderAttribute("chunk-size")
|
||||||
|
?.let(Integer::decode) ?: 0x10000
|
||||||
connection = Configuration.Connection(
|
connection = Configuration.Connection(
|
||||||
idleTimeout,
|
idleTimeout,
|
||||||
readIdleTimeout,
|
readIdleTimeout,
|
||||||
writeIdleTimeout,
|
writeIdleTimeout,
|
||||||
maxRequestSize
|
maxRequestSize,
|
||||||
|
chunkSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -40,6 +40,7 @@ object Serializer {
|
|||||||
attr("read-idle-timeout", connection.readIdleTimeout.toString())
|
attr("read-idle-timeout", connection.readIdleTimeout.toString())
|
||||||
attr("write-idle-timeout", connection.writeIdleTimeout.toString())
|
attr("write-idle-timeout", connection.writeIdleTimeout.toString())
|
||||||
attr("max-request-size", connection.maxRequestSize.toString())
|
attr("max-request-size", connection.maxRequestSize.toString())
|
||||||
|
attr("chunk-size", connection.chunkSize.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node("event-executor") {
|
node("event-executor") {
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
package net.woggioni.rbcs.server.event
|
||||||
|
|
||||||
|
class RequestCompletedEvent {
|
||||||
|
}
|
@@ -27,6 +27,9 @@ import javax.net.ssl.SSLPeerUnverifiedException
|
|||||||
|
|
||||||
@Sharable
|
@Sharable
|
||||||
object ExceptionHandler : ChannelDuplexHandler() {
|
object ExceptionHandler : ChannelDuplexHandler() {
|
||||||
|
|
||||||
|
val NAME : String = this::class.java.name
|
||||||
|
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
|
|
||||||
private val NOT_AUTHORIZED: FullHttpResponse = DefaultFullHttpResponse(
|
private val NOT_AUTHORIZED: FullHttpResponse = DefaultFullHttpResponse(
|
||||||
|
@@ -1,28 +1,79 @@
|
|||||||
package net.woggioni.rbcs.server.handler
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandler
|
||||||
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.ChannelOutboundHandler
|
||||||
|
import io.netty.channel.ChannelPromise
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
import io.netty.handler.codec.http.HttpContent
|
import io.netty.handler.codec.http.HttpContent
|
||||||
import io.netty.handler.codec.http.LastHttpContent
|
import io.netty.handler.codec.http.LastHttpContent
|
||||||
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheValueNotFoundResponse
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
||||||
|
import net.woggioni.rbcs.api.message.CacheMessage.CachePutResponse
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
||||||
|
import java.net.SocketAddress
|
||||||
|
|
||||||
@Sharable
|
class CacheContentHandler(private val pairedHandler : ChannelHandler) : SimpleChannelInboundHandler<HttpContent>(), ChannelOutboundHandler {
|
||||||
object CacheContentHandler : SimpleChannelInboundHandler<HttpContent>() {
|
private var requestFinished = false
|
||||||
val NAME = this::class.java.name
|
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpContent) {
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpContent) {
|
||||||
when(msg) {
|
if(requestFinished) {
|
||||||
is LastHttpContent -> {
|
ctx.fireChannelRead(msg.retain())
|
||||||
ctx.fireChannelRead(LastCacheContent(msg.content().retain()))
|
} else {
|
||||||
ctx.pipeline().remove(this)
|
when (msg) {
|
||||||
|
is LastHttpContent -> {
|
||||||
|
ctx.fireChannelRead(LastCacheContent(msg.content().retain()))
|
||||||
|
requestFinished = true
|
||||||
|
}
|
||||||
|
else -> ctx.fireChannelRead(CacheContent(msg.content().retain()))
|
||||||
}
|
}
|
||||||
else -> ctx.fireChannelRead(CacheContent(msg.content().retain()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
super.exceptionCaught(ctx, cause)
|
super.exceptionCaught(ctx, cause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun bind(ctx: ChannelHandlerContext, localAddress: SocketAddress, promise: ChannelPromise) {
|
||||||
|
ctx.bind(localAddress, promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun connect(
|
||||||
|
ctx: ChannelHandlerContext,
|
||||||
|
remoteAddress: SocketAddress,
|
||||||
|
localAddress: SocketAddress,
|
||||||
|
promise: ChannelPromise
|
||||||
|
) {
|
||||||
|
ctx.connect(remoteAddress, localAddress, promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disconnect(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.disconnect(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.close(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deregister(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.deregister(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(ctx: ChannelHandlerContext) {
|
||||||
|
ctx.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flush(ctx: ChannelHandlerContext) {
|
||||||
|
ctx.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
|
||||||
|
ctx.write(msg, promise)
|
||||||
|
if(msg is LastCacheContent || msg is CachePutResponse || msg is CacheValueNotFoundResponse || msg is LastHttpContent) {
|
||||||
|
ctx.pipeline().remove(pairedHandler)
|
||||||
|
ctx.pipeline().remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter
|
||||||
|
import io.netty.channel.ChannelOutboundHandler
|
||||||
|
import io.netty.channel.ChannelPromise
|
||||||
|
import net.woggioni.rbcs.server.event.RequestCompletedEvent
|
||||||
|
import java.net.SocketAddress
|
||||||
|
|
||||||
|
class ResponseCapHandler : ChannelInboundHandlerAdapter(), ChannelOutboundHandler {
|
||||||
|
val bufferedMessages = mutableListOf<Any>()
|
||||||
|
|
||||||
|
override fun bind(ctx: ChannelHandlerContext, localAddress: SocketAddress, promise: ChannelPromise) {
|
||||||
|
ctx.bind(localAddress, promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun connect(
|
||||||
|
ctx: ChannelHandlerContext,
|
||||||
|
remoteAddress: SocketAddress,
|
||||||
|
localAddress: SocketAddress,
|
||||||
|
promise: ChannelPromise
|
||||||
|
) {
|
||||||
|
ctx.connect(remoteAddress, localAddress, promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disconnect(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.disconnect(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.close(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deregister(ctx: ChannelHandlerContext, promise: ChannelPromise) {
|
||||||
|
ctx.deregister(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(ctx: ChannelHandlerContext) {
|
||||||
|
ctx.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
|
||||||
|
bufferedMessages.add(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flush(ctx: ChannelHandlerContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
||||||
|
if(evt is RequestCompletedEvent) {
|
||||||
|
for(msg in bufferedMessages) ctx.write(msg)
|
||||||
|
ctx.flush()
|
||||||
|
ctx.pipeline().remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package net.woggioni.rbcs.server.handler
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
import io.netty.channel.ChannelDuplexHandler
|
import io.netty.channel.ChannelDuplexHandler
|
||||||
|
import io.netty.channel.ChannelHandler
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.ChannelPromise
|
import io.netty.channel.ChannelPromise
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
||||||
@@ -15,6 +16,8 @@ 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.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 net.woggioni.rbcs.api.CacheHandlerFactory
|
||||||
import net.woggioni.rbcs.api.CacheValueMetadata
|
import net.woggioni.rbcs.api.CacheValueMetadata
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage
|
import net.woggioni.rbcs.api.message.CacheMessage
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
||||||
@@ -27,19 +30,22 @@ import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
|||||||
import net.woggioni.rbcs.common.createLogger
|
import net.woggioni.rbcs.common.createLogger
|
||||||
import net.woggioni.rbcs.common.debug
|
import net.woggioni.rbcs.common.debug
|
||||||
import net.woggioni.rbcs.common.warn
|
import net.woggioni.rbcs.common.warn
|
||||||
|
import net.woggioni.rbcs.server.event.RequestCompletedEvent
|
||||||
|
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class ServerHandler(private val serverPrefix: Path) :
|
class ServerHandler(private val serverPrefix: Path, private val cacheHandlerSupplier : () -> ChannelHandler) :
|
||||||
ChannelDuplexHandler() {
|
ChannelDuplexHandler() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = createLogger<ServerHandler>()
|
private val log = createLogger<ServerHandler>()
|
||||||
val NAME = this::class.java.name
|
val NAME = ServerHandler::class.java.name
|
||||||
}
|
}
|
||||||
|
|
||||||
private var httpVersion = HttpVersion.HTTP_1_1
|
private var httpVersion = HttpVersion.HTTP_1_1
|
||||||
private var keepAlive = true
|
private var keepAlive = true
|
||||||
|
private var pipelinedRequests = 0
|
||||||
|
|
||||||
private fun resetRequestMetadata() {
|
private fun resetRequestMetadata() {
|
||||||
httpVersion = HttpVersion.HTTP_1_1
|
httpVersion = HttpVersion.HTTP_1_1
|
||||||
@@ -73,6 +79,8 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
try {
|
try {
|
||||||
when (msg) {
|
when (msg) {
|
||||||
is CachePutResponse -> {
|
is CachePutResponse -> {
|
||||||
|
pipelinedRequests -= 1
|
||||||
|
ctx.fireUserEventTriggered(RequestCompletedEvent())
|
||||||
val response = DefaultFullHttpResponse(httpVersion, HttpResponseStatus.CREATED)
|
val response = DefaultFullHttpResponse(httpVersion, HttpResponseStatus.CREATED)
|
||||||
val keyBytes = msg.key.toByteArray(Charsets.UTF_8)
|
val keyBytes = msg.key.toByteArray(Charsets.UTF_8)
|
||||||
response.headers().apply {
|
response.headers().apply {
|
||||||
@@ -88,6 +96,8 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
is CacheValueNotFoundResponse -> {
|
is CacheValueNotFoundResponse -> {
|
||||||
|
pipelinedRequests -= 1
|
||||||
|
ctx.fireUserEventTriggered(RequestCompletedEvent())
|
||||||
val response = DefaultFullHttpResponse(httpVersion, HttpResponseStatus.NOT_FOUND)
|
val response = DefaultFullHttpResponse(httpVersion, HttpResponseStatus.NOT_FOUND)
|
||||||
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
||||||
setKeepAliveHeader(response.headers())
|
setKeepAliveHeader(response.headers())
|
||||||
@@ -108,6 +118,8 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
is LastCacheContent -> {
|
is LastCacheContent -> {
|
||||||
|
pipelinedRequests -= 1
|
||||||
|
ctx.fireUserEventTriggered(RequestCompletedEvent())
|
||||||
ctx.writeAndFlush(DefaultLastHttpContent(msg.content()))
|
ctx.writeAndFlush(DefaultLastHttpContent(msg.content()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +139,10 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
} finally {
|
} finally {
|
||||||
resetRequestMetadata()
|
resetRequestMetadata()
|
||||||
}
|
}
|
||||||
|
} else if(msg is LastHttpContent) {
|
||||||
|
pipelinedRequests -= 1
|
||||||
|
ctx.fireUserEventTriggered(RequestCompletedEvent())
|
||||||
|
ctx.write(msg, promise)
|
||||||
} else super.write(ctx, msg, promise)
|
} else super.write(ctx, msg, promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +155,13 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
if (path.startsWith(serverPrefix)) {
|
if (path.startsWith(serverPrefix)) {
|
||||||
val relativePath = serverPrefix.relativize(path)
|
val relativePath = serverPrefix.relativize(path)
|
||||||
val key = relativePath.toString()
|
val key = relativePath.toString()
|
||||||
ctx.pipeline().addAfter(NAME, CacheContentHandler.NAME, CacheContentHandler)
|
if(pipelinedRequests > 0) {
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, ResponseCapHandler())
|
||||||
|
}
|
||||||
|
val cacheHandler = cacheHandlerSupplier()
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, CacheContentHandler(cacheHandler))
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, cacheHandler)
|
||||||
|
pipelinedRequests += 1
|
||||||
key.let(::CacheGetRequest)
|
key.let(::CacheGetRequest)
|
||||||
.let(ctx::fireChannelRead)
|
.let(ctx::fireChannelRead)
|
||||||
?: ctx.channel().write(CacheValueNotFoundResponse())
|
?: ctx.channel().write(CacheValueNotFoundResponse())
|
||||||
@@ -159,7 +181,14 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Added value for key '$key' to build cache"
|
"Added value for key '$key' to build cache"
|
||||||
}
|
}
|
||||||
ctx.pipeline().addAfter(NAME, CacheContentHandler.NAME, CacheContentHandler)
|
if(pipelinedRequests > 0) {
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, ResponseCapHandler())
|
||||||
|
}
|
||||||
|
val cacheHandler = cacheHandlerSupplier()
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, CacheContentHandler(cacheHandler))
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, cacheHandler)
|
||||||
|
pipelinedRequests += 1
|
||||||
|
|
||||||
path.fileName?.toString()
|
path.fileName?.toString()
|
||||||
?.let {
|
?.let {
|
||||||
val mimeType = HttpUtil.getMimeType(msg)?.toString()
|
val mimeType = HttpUtil.getMimeType(msg)?.toString()
|
||||||
@@ -176,6 +205,11 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
ctx.writeAndFlush(response)
|
ctx.writeAndFlush(response)
|
||||||
}
|
}
|
||||||
} else if (method == HttpMethod.TRACE) {
|
} else if (method == HttpMethod.TRACE) {
|
||||||
|
if(pipelinedRequests > 0) {
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, ResponseCapHandler())
|
||||||
|
}
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, TraceHandler)
|
||||||
|
pipelinedRequests += 1
|
||||||
super.channelRead(ctx, msg)
|
super.channelRead(ctx, msg)
|
||||||
} else {
|
} else {
|
||||||
log.warn(ctx) {
|
log.warn(ctx) {
|
||||||
@@ -187,42 +221,6 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class ContentDisposition(val type: Type?, val fileName: String?) {
|
|
||||||
enum class Type {
|
|
||||||
attachment, `inline`;
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun parse(maybeString: String?) = maybeString.let { s ->
|
|
||||||
try {
|
|
||||||
java.lang.Enum.valueOf(Type::class.java, s)
|
|
||||||
} catch (ex: IllegalArgumentException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun parse(contentDisposition: String) : ContentDisposition {
|
|
||||||
val parts = contentDisposition.split(";").dropLastWhile { it.isEmpty() }.toTypedArray()
|
|
||||||
val dispositionType = parts[0].trim { it <= ' ' }.let(Type::parse) // Get the type (e.g., attachment)
|
|
||||||
|
|
||||||
var filename: String? = null
|
|
||||||
for (i in 1..<parts.size) {
|
|
||||||
val part = parts[i].trim { it <= ' ' }
|
|
||||||
if (part.lowercase(Locale.getDefault()).startsWith("filename=")) {
|
|
||||||
filename = part.substring("filename=".length).trim { it <= ' ' }.replace("\"", "")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ContentDisposition(dispositionType, filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
super.exceptionCaught(ctx, cause)
|
super.exceptionCaught(ctx, cause)
|
||||||
}
|
}
|
||||||
|
@@ -42,6 +42,7 @@ object TraceHandler : ChannelInboundHandlerAdapter() {
|
|||||||
}
|
}
|
||||||
is LastHttpContent -> {
|
is LastHttpContent -> {
|
||||||
ctx.writeAndFlush(msg)
|
ctx.writeAndFlush(msg)
|
||||||
|
ctx.pipeline().remove(this)
|
||||||
}
|
}
|
||||||
is HttpContent -> ctx.writeAndFlush(msg)
|
is HttpContent -> ctx.writeAndFlush(msg)
|
||||||
else -> super.channelRead(ctx, msg)
|
else -> super.channelRead(ctx, msg)
|
||||||
|
@@ -115,6 +115,14 @@
|
|||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="chunk-size" type="rbcs:byteSizeType" default="0x10000">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Maximum byte size of socket write calls
|
||||||
|
(reduce it to reduce memory consumption, increase it for increased throughput)
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="eventExecutorType">
|
<xs:complexType name="eventExecutorType">
|
||||||
@@ -175,13 +183,6 @@
|
|||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="chunk-size" type="rbcs:byteSizeType" default="0x10000">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>
|
|
||||||
Maximum byte size of socket write calls
|
|
||||||
</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
</xs:extension>
|
</xs:extension>
|
||||||
</xs:complexContent>
|
</xs:complexContent>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
@@ -231,14 +232,6 @@
|
|||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="chunk-size" type="rbcs:byteSizeType" default="0x10000">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>
|
|
||||||
Maximum byte size of a cache value that will be stored in memory
|
|
||||||
(reduce it to reduce memory consumption, increase it for increased throughput)
|
|
||||||
</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
</xs:extension>
|
</xs:extension>
|
||||||
</xs:complexContent>
|
</xs:complexContent>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
@@ -41,7 +41,8 @@ abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
|||||||
Duration.of(60, ChronoUnit.SECONDS),
|
Duration.of(60, ChronoUnit.SECONDS),
|
||||||
Duration.of(30, ChronoUnit.SECONDS),
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
Duration.of(30, ChronoUnit.SECONDS),
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
0x1000
|
0x1000,
|
||||||
|
0x10000
|
||||||
),
|
),
|
||||||
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(),
|
||||||
@@ -50,8 +51,7 @@ abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
|||||||
maxAge = Duration.ofSeconds(3600 * 24),
|
maxAge = Duration.ofSeconds(3600 * 24),
|
||||||
digestAlgorithm = "MD5",
|
digestAlgorithm = "MD5",
|
||||||
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
||||||
compressionEnabled = false,
|
compressionEnabled = false
|
||||||
chunkSize = 0x1000
|
|
||||||
),
|
),
|
||||||
Configuration.BasicAuthentication(),
|
Configuration.BasicAuthentication(),
|
||||||
null,
|
null,
|
||||||
|
@@ -147,7 +147,8 @@ abstract class AbstractTlsServerTest : AbstractServerTest() {
|
|||||||
Duration.of(60, ChronoUnit.SECONDS),
|
Duration.of(60, ChronoUnit.SECONDS),
|
||||||
Duration.of(30, ChronoUnit.SECONDS),
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
Duration.of(30, ChronoUnit.SECONDS),
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
0x1000
|
0x1000,
|
||||||
|
0x10000
|
||||||
),
|
),
|
||||||
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(),
|
||||||
@@ -156,7 +157,6 @@ abstract class AbstractTlsServerTest : AbstractServerTest() {
|
|||||||
compressionEnabled = false,
|
compressionEnabled = false,
|
||||||
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
||||||
digestAlgorithm = "MD5",
|
digestAlgorithm = "MD5",
|
||||||
chunkSize = 0x1000
|
|
||||||
),
|
),
|
||||||
// InMemoryCacheConfiguration(
|
// InMemoryCacheConfiguration(
|
||||||
// maxAge = Duration.ofSeconds(3600 * 24),
|
// maxAge = Duration.ofSeconds(3600 * 24),
|
||||||
|
@@ -41,7 +41,8 @@ class NoAuthServerTest : AbstractServerTest() {
|
|||||||
Duration.of(60, ChronoUnit.SECONDS),
|
Duration.of(60, ChronoUnit.SECONDS),
|
||||||
Duration.of(30, ChronoUnit.SECONDS),
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
Duration.of(30, ChronoUnit.SECONDS),
|
Duration.of(30, ChronoUnit.SECONDS),
|
||||||
0x1000
|
0x1000,
|
||||||
|
0x10000
|
||||||
),
|
),
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
@@ -51,7 +52,6 @@ class NoAuthServerTest : AbstractServerTest() {
|
|||||||
digestAlgorithm = "MD5",
|
digestAlgorithm = "MD5",
|
||||||
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
||||||
maxSize = 0x1000000,
|
maxSize = 0x1000000,
|
||||||
chunkSize = 0x1000
|
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@@ -7,9 +7,10 @@
|
|||||||
read-idle-timeout="PT10M"
|
read-idle-timeout="PT10M"
|
||||||
write-idle-timeout="PT11M"
|
write-idle-timeout="PT11M"
|
||||||
idle-timeout="PT30M"
|
idle-timeout="PT30M"
|
||||||
max-request-size="101325"/>
|
max-request-size="101325"
|
||||||
|
chunk-size="0xa910"/>
|
||||||
<event-executor use-virtual-threads="false"/>
|
<event-executor use-virtual-threads="false"/>
|
||||||
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D" chunk-size="0xa910"/>
|
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D"/>
|
||||||
<authentication>
|
<authentication>
|
||||||
<none/>
|
<none/>
|
||||||
</authentication>
|
</authentication>
|
||||||
|
@@ -9,9 +9,10 @@
|
|||||||
max-request-size="67108864"
|
max-request-size="67108864"
|
||||||
idle-timeout="PT30S"
|
idle-timeout="PT30S"
|
||||||
read-idle-timeout="PT60S"
|
read-idle-timeout="PT60S"
|
||||||
write-idle-timeout="PT60S"/>
|
write-idle-timeout="PT60S"
|
||||||
|
chunk-size="123"/>
|
||||||
<event-executor use-virtual-threads="true"/>
|
<event-executor use-virtual-threads="true"/>
|
||||||
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" chunk-size="123">
|
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D">
|
||||||
<server host="memcached" port="11211"/>
|
<server host="memcached" port="11211"/>
|
||||||
</cache>
|
</cache>
|
||||||
<authorization>
|
<authorization>
|
||||||
|
@@ -8,9 +8,10 @@
|
|||||||
read-idle-timeout="PT10M"
|
read-idle-timeout="PT10M"
|
||||||
write-idle-timeout="PT11M"
|
write-idle-timeout="PT11M"
|
||||||
idle-timeout="PT30M"
|
idle-timeout="PT30M"
|
||||||
max-request-size="101325"/>
|
max-request-size="101325"
|
||||||
|
chunk-size="456"/>
|
||||||
<event-executor use-virtual-threads="false"/>
|
<event-executor use-virtual-threads="false"/>
|
||||||
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" digest="SHA-256" chunk-size="456" compression-mode="deflate" compression-level="7">
|
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" digest="SHA-256" compression-mode="deflate" compression-level="7">
|
||||||
<server host="127.0.0.1" port="11211" max-connections="10" connection-timeout="PT20S"/>
|
<server host="127.0.0.1" port="11211" max-connections="10" connection-timeout="PT20S"/>
|
||||||
</cache>
|
</cache>
|
||||||
<authentication>
|
<authentication>
|
||||||
|
@@ -7,9 +7,10 @@
|
|||||||
read-idle-timeout="PT10M"
|
read-idle-timeout="PT10M"
|
||||||
write-idle-timeout="PT11M"
|
write-idle-timeout="PT11M"
|
||||||
idle-timeout="PT30M"
|
idle-timeout="PT30M"
|
||||||
max-request-size="4096"/>
|
max-request-size="4096"
|
||||||
|
chunk-size="0xa91f"/>
|
||||||
<event-executor use-virtual-threads="false"/>
|
<event-executor use-virtual-threads="false"/>
|
||||||
<cache xs:type="rbcs:inMemoryCacheType" max-age="P7D" chunk-size="0xa91f"/>
|
<cache xs:type="rbcs:inMemoryCacheType" max-age="P7D"/>
|
||||||
<authorization>
|
<authorization>
|
||||||
<users>
|
<users>
|
||||||
<user name="user1" password="password1">
|
<user name="user1" password="password1">
|
||||||
|
Reference in New Issue
Block a user