removed readTimeout and writeTimeout from server configuration

added Markdown documentation
This commit is contained in:
2025-02-07 10:18:54 +08:00
parent 591f6e2af4
commit 619873c4a9
17 changed files with 721 additions and 111 deletions

20
LICENSE Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 Y. T. CHUNG <zonyitoo@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

165
README.md
View File

@@ -0,0 +1,165 @@
# Remote Build Cache Server
Remote Build Cache Server (shortened to RBCS) allows you to share and reuse unchanged build
and test outputs across the team. This speeds up local and CI builds since cycles are not wasted
re-building components that are unaffected by new code changes. RBCS supports both Gradle and
Maven build tool environments.
It comes with pluggable storage backends, the core application offers in-memory storage or disk-backed storage,
in addition to this there is an official plugin to use memcached as the storage backend.
It supports HTTP basic authentication or, alternatively, TLS certificate authentication, role-based access control (RBAC),
and throttling.
## Quickstart
### Downloading the jar file
You can download the latest version from [this link](https://gitea.woggioni.net/woggioni/-/packages/maven/net.woggioni:rbcs-cli/)
Assuming you have Java 21 or later installed, you can launch the server directly with
```bash
java -jar rbcs-cli.jar server
```
By default it will start an HTTP server bound to localhost and listening on port 8080 with no authentication,
writing data to the disk, that you can use for testing
### Using the Docker image
You can pull the latest Docker image with
```bash
docker pull gitea.woggioni.net/woggioni/rbcs:latest
```
By default it will start an HTTP server bound to localhost and listening on port 8080 with no authentication,
writing data to the disk, that you can use for testing
## Usage
### Configuration
The location of the `rbcs.xml` configuration file depends on the operating system,
Alternatively it can be changed setting the `RBCS_CONFIGURATION_DIR` environmental variable or `net.woggioni.rbcs.conf.dir` Java system property
to the directory that contain the `rbcs.xml` file.
The server configuration file follows the XML format and uses XML schema for validation
(you can find the schema for the main configuration file [here](https://gitea.woggioni.net/woggioni/rbcs/src/branch/master/rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs.xsd)).
The configuration values are enclosed inside XML attribute and support system property / environmental variable interpolation.
As an example, you can configure RBCS to read the server port number from the `RBCS_SERVER_PORT` environmental variable
and the bind address from the `rbc.bind.address` JVM system property with.
Full documentation for all tags and attributes is available [here](doc/server_configuration.md).
### Plugins
If you want to use memcache as a storage backend you'll also need to download [the memcache plugin](https://gitea.woggioni.net/woggioni/-/packages/maven/net.woggioni:rbcs-server-memcache/)
Plugins need to be stored in a folder named `plugins` in the located server's working directory
(the directory where the server process is started). They are shipped as TAR archives, so you need to extract
the content of the archive into the `plugins` directory for the server to pick them up.
### Using RBCS with Gradle
Add this to the `settings.gradle` file of your project
```groovy
buildCache {
remote(HttpBuildCache) {
url = 'https://rbcs.example.com/'
push = true
allowInsecureProtocol = false
}
}
```
alternatively you can add this to `${GRADLE_HOME}/init.gradle`
```groovy
gradle.settingsEvaluated { settings ->
settings.buildCache {
remote(HttpBuildCache) {
url = 'https://rbcs.example.com/'
push = true
allowInsecureProtocol = false
}
}
}
```
### Using RBCS with Maven
Read [here](https://maven.apache.org/extensions/maven-build-cache-extension/remote-cache.html)
## FAQ
### Why should I use a build cache?
#### Build Caches Improve Build & Test Performance
Building software consists of a number of steps, like compiling sources, executing tests, and linking binaries. Weve seen that a binary artifact repository helps when such a step requires an external component by downloading the artifact from the repository rather than building it locally.
However, there are many additional steps in this build process which can be optimized to reduce the build time. An obvious strategy is to avoid executing build steps which dominate the total build time when these build steps are not needed.
Most build times are dominated by the testing step.
While binary repositories cannot capture the outcome of a test build step (only the test reports
when included in binary artifacts), build caches are designed to eliminate redundant executions
for every build step. Moreover, it generalizes the concept of avoiding work associated with any
incremental step of the build, including test execution, compilation and resource processing.
The mechanism itself is comparable to a pure function. That is, given some inputs such as source
files and environment parameters we know that the output is always going to be the same.
As a result, we can cache it and retrieve it based on a simple cryptographic hash of the inputs.
Build caching is supported natively by some build tools.
#### Improve CI builds with a remote build cache
When analyzing the role of a build cache it is important to take into account the granularity
of the changes that it caches. Imagine a full build for a project with 40 to 50 modules
which fails at the last step (deployment) because the staging environment is temporarily unavailable.
Although the vast majority of the build steps (potentially thousands) succeed,
the change can not be deployed to the staging environment.
Without a build cache one typically relies on a very complex CI configuration to reuse build step outputs
or would have to repeat the full build once the environment is available.
Some build tools dont support incremental builds properly. For example, outputs of a build started
from scratch may vary when compared to subsequent builds that rely on the initial builds output.
As a result, to preserve build integrity, its crucial to rebuild from scratch, or cleanly, in this
scenario.
With a build cache, only the last step needs to be executed and the build can be re-triggered
when the environment is back online. This automatically saves all of the time and
resources required across the different build steps which were successfully executed.
Instead of executing the intermediate steps, the build tool pulls the outputs from the build cache,
avoiding a lot of redundant work
#### Share outputs with a remote build cache
One of the most important advantages of a remote build cache is the ability to share build outputs.
In most CI configurations, for example, a number of pipelines are created.
These may include one for building the sources, one for testing, one for publishing the outcomes
to a remote repository, and other pipelines to test on different platforms.
There are even situations where CI builds partially build a project (i.e. some modules and not others).
Most of those pipelines share a lot of intermediate build steps. All builds which perform testing
require the binaries to be ready. All publishing builds require all previous steps to be executed.
And because modern CI infrastructure means executing everything in containerized (isolated) environments,
significant resources are wasted by repeatedly building the same intermediate artifacts.
A remote build cache greatly reduces this overhead by orders of magnitudes because it provides a way
for all those pipelines to share their outputs. After all, there is no point recreating an output that
is already available in the cache.
Because there are inherent dependencies between software components of a build,
introducing a build cache dramatically reduces the impact of exploding a component into multiple pieces,
allowing for increased modularity without increased overhead.
#### Make local developers more efficient with remote build caches
It is common for different teams within a company to work on different modules of a single large
application. In this case, most teams dont care about building the other parts of the software.
By introducing a remote cache developers immediately benefit from pre-built artifacts when checking out code.
Because it has already been built on CI, they dont have to do it locally.
Introducing a remote cache is a huge benefit for those developers. Consider that a typical developers
day begins by performing a code checkout. Most likely the checked out code has already been built on CI.
Therefore, no time is wasted running the first build of the day. The remote cache provides all of the
intermediate artifacts needed. And, in the event local changes are made, the remote cache still leverages
partial cache hits for projects which are independent. As other developers in the organization request
CI builds, the remote cache continues to populate, increasing the likelihood of these remote cache hits
across team members.

View File

@@ -35,8 +35,6 @@ public class Configuration {
@Value @Value
public static class Connection { public static class Connection {
Duration readTimeout;
Duration writeTimeout;
Duration idleTimeout; Duration idleTimeout;
Duration readIdleTimeout; Duration readIdleTimeout;
Duration writeIdleTimeout; Duration writeIdleTimeout;

View File

@@ -59,6 +59,9 @@ class ServerCommand(app : Application) : RbcsCommand() {
createDefaultConfigurationFile(configurationFile) createDefaultConfigurationFile(configurationFile)
} }
log.debug {
"Using configuration file '$configurationFile'"
}
val configuration = RemoteBuildCacheServer.loadConfiguration(configurationFile) val configuration = RemoteBuildCacheServer.loadConfiguration(configurationFile)
log.debug { log.debug {
ByteArrayOutputStream().also { ByteArrayOutputStream().also {

View File

@@ -0,0 +1,46 @@
# RBCS Memcache plugins
This plugins allows RBCs to store and retrieve data from a memcache cluster.
The memcache server selection is simply based on the hash of the key,
deflate compression is also supported and performed by the RBCS server
## Quickstart
The plugin can be built with
```bash
./gradlew rbcs-server-memcache:bundle
```
which creates a `.tar` archive in the `build/distributions` folder.
The archive is supposed to be extracted inside the RBCS server's `plugins` directory.
## Configuration
The plugin can be enabled setting the `xs:type` attribute of the `cache` element
to `memcacheCacheType`.
The plugins currently supports the following configuration attributes:
- `max-age`: the amount of time cache entries will be retained on memcache
- `digest`: digest algorithm to use on the key before submission
to memcache (optional, no digest is applied if omitted)
- `compression`: compression algorithm to apply to cache values before,
currently only `deflate` is supported (optionla, if omitted compression is disabled)
- `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)
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xmlns:rbcs-memcache="urn:net.woggioni.rbcs.server.memcache"
xs:schemaLocation="urn:net.woggioni.rbcs.server.memcache jpms://net.woggioni.rbcs.server.memcache/net/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd"
>
...
<cache xs:type="rbcs-memcache:memcacheCacheType"
max-age="P7D"
digest="SHA-256"
compression-mode="deflate"
compression-level="6"
chunk-size="0x10000">
<server host="127.0.0.1" port="11211" max-connections="256"/>
<server host="127.0.0.1" port="11212" max-connections="256"/>
</cache>
...
```

View File

@@ -314,19 +314,6 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
} }
val pipeline = ch.pipeline() val pipeline = ch.pipeline()
cfg.connection.also { conn -> cfg.connection.also { conn ->
val readTimeout = conn.readTimeout.toMillis()
val writeTimeout = conn.writeTimeout.toMillis()
if (readTimeout > 0 || writeTimeout > 0) {
pipeline.addLast(
IdleStateHandler(
false,
readTimeout,
writeTimeout,
0,
TimeUnit.MILLISECONDS
)
)
}
val readIdleTimeout = conn.readIdleTimeout.toMillis() val readIdleTimeout = conn.readIdleTimeout.toMillis()
val writeIdleTimeout = conn.writeIdleTimeout.toMillis() val writeIdleTimeout = conn.writeIdleTimeout.toMillis()
val idleTimeout = conn.idleTimeout.toMillis() val idleTimeout = conn.idleTimeout.toMillis()

View File

@@ -27,8 +27,6 @@ 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(10, ChronoUnit.SECONDS),
Duration.of(10, ChronoUnit.SECONDS),
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),
@@ -113,10 +111,6 @@ object Parser {
} }
"connection" -> { "connection" -> {
val writeTimeout = child.renderAttribute("write-timeout")
?.let(Duration::parse) ?: Duration.of(0, ChronoUnit.SECONDS)
val readTimeout = child.renderAttribute("read-timeout")
?.let(Duration::parse) ?: Duration.of(0, ChronoUnit.SECONDS)
val idleTimeout = child.renderAttribute("idle-timeout") val idleTimeout = child.renderAttribute("idle-timeout")
?.let(Duration::parse) ?: Duration.of(30, ChronoUnit.SECONDS) ?.let(Duration::parse) ?: Duration.of(30, ChronoUnit.SECONDS)
val readIdleTimeout = child.renderAttribute("read-idle-timeout") val readIdleTimeout = child.renderAttribute("read-idle-timeout")
@@ -126,8 +120,6 @@ object Parser {
val maxRequestSize = child.renderAttribute("max-request-size") val maxRequestSize = child.renderAttribute("max-request-size")
?.let(Integer::decode) ?: 0x4000000 ?.let(Integer::decode) ?: 0x4000000
connection = Configuration.Connection( connection = Configuration.Connection(
readTimeout,
writeTimeout,
idleTimeout, idleTimeout,
readIdleTimeout, readIdleTimeout,
writeIdleTimeout, writeIdleTimeout,

View File

@@ -36,8 +36,6 @@ object Serializer {
} }
node("connection") { node("connection") {
conf.connection.let { connection -> conf.connection.let { connection ->
attr("read-timeout", connection.readTimeout.toString())
attr("write-timeout", connection.writeTimeout.toString())
attr("idle-timeout", connection.idleTimeout.toString()) attr("idle-timeout", connection.idleTimeout.toString())
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())

View File

@@ -4,16 +4,5 @@
xmlns:rbcs="urn:net.woggioni.rbcs.server" xmlns:rbcs="urn:net.woggioni.rbcs.server"
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd"> xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd">
<bind host="127.0.0.1" port="8080" incoming-connections-backlog-size="1024"/> <bind host="127.0.0.1" port="8080" incoming-connections-backlog-size="1024"/>
<connection <cache xs:type="rbcs:fileSystemCacheType" path="${sys:java.io.tmpdir}/rbcs" max-age="P7D"/>
max-request-size="67108864"
idle-timeout="PT30S"
read-timeout="PT10S"
write-timeout="PT10S"
read-idle-timeout="PT60S"
write-idle-timeout="PT60S"/>
<event-executor use-virtual-threads="true"/>
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D"/>
<authentication>
<none/>
</authentication>
</rbcs:server> </rbcs:server>

View File

@@ -3,14 +3,27 @@
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:rbcs="urn:net.woggioni.rbcs.server" xmlns:rbcs="urn:net.woggioni.rbcs.server"
elementFormDefault="unqualified"> elementFormDefault="unqualified">
<xs:element name="server" type="rbcs:serverType"/> <xs:element name="server" type="rbcs:serverType">
<xs:annotation>
<xs:documentation>
Root element containing the server configuration
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="serverType"> <xs:complexType name="serverType">
<xs:sequence minOccurs="0"> <xs:sequence minOccurs="0">
<xs:element name="bind" type="rbcs:bindType" maxOccurs="1"/> <xs:element name="bind" type="rbcs:bindType" maxOccurs="1"/>
<xs:element name="connection" type="rbcs:connectionType" minOccurs="0" maxOccurs="1"/> <xs:element name="connection" type="rbcs:connectionType" minOccurs="0" maxOccurs="1"/>
<xs:element name="event-executor" type="rbcs:eventExecutorType" minOccurs="0" maxOccurs="1"/> <xs:element name="event-executor" type="rbcs:eventExecutorType" minOccurs="0" maxOccurs="1"/>
<xs:element name="cache" type="rbcs:cacheType" maxOccurs="1"/> <xs:element name="cache" type="rbcs:cacheType" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Cache storage backend implementation to use, more implementations can be added through
the use of plugins
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="authorization" type="rbcs:authorizationType" minOccurs="0"> <xs:element name="authorization" type="rbcs:authorizationType" minOccurs="0">
<xs:key name="userId"> <xs:key name="userId">
<xs:selector xpath="users/user"/> <xs:selector xpath="users/user"/>
@@ -21,75 +34,279 @@
<xs:field xpath="@ref"/> <xs:field xpath="@ref"/>
</xs:keyref> </xs:keyref>
</xs:element> </xs:element>
<xs:element name="authentication" type="rbcs:authenticationType" minOccurs="0" maxOccurs="1"/> <xs:element name="authentication" type="rbcs:authenticationType" minOccurs="0" maxOccurs="1">
<xs:element name="tls" type="rbcs:tlsType" minOccurs="0" maxOccurs="1"/> <xs:annotation>
<xs:documentation>
Mechanism to use to assign a username to a specific client
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="tls" type="rbcs:tlsType" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Use TLS to encrypt all the communications
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence> </xs:sequence>
<xs:attribute name="path" type="xs:string" use="optional"/> <xs:attribute name="path" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>
URI path prefix, if your rbcs is hosted at "http://www.example.com"
and this parameter is set to "cache", then all the requests will need to be sent at
"http://www.example.com/cache/KEY", where "KEY" is the cache entry KEY
</xs:documentation>
</xs:annotation>
</xs:attribute>
</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:annotation>
<xs:attribute name="incoming-connections-backlog-size" type="xs:unsignedInt" use="optional" default="1024"/> <xs:documentation>Server bind address</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="port" type="xs:unsignedShort" use="required">
<xs:annotation>
<xs:documentation>Server port number</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="incoming-connections-backlog-size" type="xs:unsignedInt" use="optional" default="1024">
<xs:annotation>
<xs:documentation>
The maximum queue length for incoming connection indications (a request to connect) is set to
the backlog parameter. If a connection indication arrives when the queue is full,
the connection is refused.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="connectionType"> <xs:complexType name="connectionType">
<xs:attribute name="read-timeout" type="xs:duration" use="optional" default="PT0S"/> <xs:attribute name="idle-timeout" type="xs:duration" use="optional" default="PT30S">
<xs:attribute name="write-timeout" type="xs:duration" use="optional" default="PT0S"/> <xs:annotation>
<xs:attribute name="idle-timeout" type="xs:duration" use="optional" default="PT30S"/> <xs:documentation>
<xs:attribute name="read-idle-timeout" type="xs:duration" use="optional" default="PT60S"/> The server will close the connection with the client
<xs:attribute name="write-idle-timeout" type="xs:duration" use="optional" default="PT60S"/> when neither a read nor a write was performed for the specified period of time.
<xs:attribute name="max-request-size" type="rbcs:byteSizeType" use="optional" default="0x4000000"/> </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="read-idle-timeout" type="xs:duration" use="optional" default="PT60S">
<xs:annotation>
<xs:documentation>
The server will close the connection with the client
when no read was performed for the specified period of time.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="write-idle-timeout" type="xs:duration" use="optional" default="PT60S">
<xs:annotation>
<xs:documentation>
The server will close the connection with the client
when no write was performed for the specified period of time.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="max-request-size" type="rbcs:byteSizeType" use="optional" default="0x4000000">
<xs:annotation>
<xs:documentation>
The maximum request body size the server will accept from a client
(if exceeded the server returns 413 HTTP status code)
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="eventExecutorType"> <xs:complexType name="eventExecutorType">
<xs:attribute name="use-virtual-threads" type="xs:boolean" use="optional" default="true"/> <xs:attribute name="use-virtual-threads" type="xs:boolean" use="optional" default="true">
<xs:annotation>
<xs:documentation>
Whether or not to use virtual threads for the execution of the core server handler
(not for the I/O operations)
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="cacheType" abstract="true"/> <xs:complexType name="cacheType" abstract="true"/>
<xs:complexType name="inMemoryCacheType"> <xs:complexType name="inMemoryCacheType">
<xs:annotation>
<xs:documentation>
A simple cache implementation that uses a java.util.ConcurrentHashMap as a storage backend
</xs:documentation>
</xs:annotation>
<xs:complexContent> <xs:complexContent>
<xs:extension base="rbcs:cacheType"> <xs:extension base="rbcs:cacheType">
<xs:attribute name="max-age" type="xs:duration" default="P1D"/> <xs:attribute name="max-age" type="xs:duration" default="P1D">
<xs:attribute name="max-size" type="rbcs:byteSizeType" default="0x1000000"/> <xs:annotation>
<xs:attribute name="digest" type="xs:token" default="MD5"/> <xs:documentation>
<xs:attribute name="enable-compression" type="xs:boolean" default="true"/> Values will be removed from the cache after this amount of time
<xs:attribute name="compression-level" type="rbcs:compressionLevelType" default="-1"/> </xs:documentation>
<xs:attribute name="chunk-size" type="rbcs:byteSizeType" default="0x10000"/> </xs:annotation>
</xs:attribute>
<xs:attribute name="max-size" type="rbcs:byteSizeType" default="0x1000000">
<xs:annotation>
<xs:documentation>
The maximum allowed total size of the cache in bytes, old values will be purged from the cache
when the insertion of a new value causes this limit to be exceeded
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="digest" type="xs:token" default="MD5">
<xs:annotation>
<xs:documentation>
Hashing algorithm to apply to the key. If omitted, no hashing is performed.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="enable-compression" type="xs:boolean" default="true">
<xs:annotation>
<xs:documentation>
Enable deflate compression for stored cache elements
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="compression-level" type="rbcs:compressionLevelType" default="-1">
<xs:annotation>
<xs:documentation>
Deflate compression level to use for cache compression,
use -1 to use the default compression level of java.util.zip.Deflater
</xs:documentation>
</xs:annotation>
</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>
<xs:complexType name="fileSystemCacheType"> <xs:complexType name="fileSystemCacheType">
<xs:annotation>
<xs:documentation>
A simple cache implementation that stores data in a folder on the filesystem
</xs:documentation>
</xs:annotation>
<xs:complexContent> <xs:complexContent>
<xs:extension base="rbcs:cacheType"> <xs:extension base="rbcs:cacheType">
<xs:attribute name="path" type="xs:string" use="optional"/> <xs:attribute name="path" type="xs:string" use="optional">
<xs:attribute name="max-age" type="xs:duration" default="P1D"/> <xs:annotation>
<xs:attribute name="digest" type="xs:token" default="MD5"/> <xs:documentation>
<xs:attribute name="enable-compression" type="xs:boolean" default="true"/> File system path that will be used to store the cache data files
<xs:attribute name="compression-level" type="rbcs:compressionLevelType" default="-1"/> (it will be created if it doesn't already exist)
<xs:attribute name="chunk-size" type="rbcs:byteSizeType" default="0x10000"/> </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="max-age" type="xs:duration" default="P1D">
<xs:annotation>
<xs:documentation>
Values will be removed from the cache after this amount of time
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="digest" type="xs:token" default="MD5">
<xs:annotation>
<xs:documentation>
Hashing algorithm to apply to the key. If omitted, no hashing is performed.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="enable-compression" type="xs:boolean" default="true">
<xs:annotation>
<xs:documentation>
Enable deflate compression for stored cache elements
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="compression-level" type="rbcs:compressionLevelType" default="-1">
<xs:annotation>
<xs:documentation>
Deflate compression level to use for cache compression,
use -1 to use the default compression level of java.util.zip.Deflater
</xs:documentation>
</xs:annotation>
</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>
<xs:complexType name="tlsCertificateAuthorizationType"> <xs:complexType name="tlsCertificateAuthorizationType">
<xs:sequence> <xs:sequence>
<xs:element name="group-extractor" type="rbcs:X500NameExtractorType" minOccurs="0"/> <xs:element name="group-extractor" type="rbcs:X500NameExtractorType" minOccurs="0">
<xs:element name="user-extractor" type="rbcs:X500NameExtractorType" minOccurs="0"/> <xs:annotation>
<xs:documentation>
A regex based extractor that will be used to determine which group the client belongs to,
based on the X.500 name of the subject field in the client's TLS certificate.
When this is set RBAC works even if the user isn't listed in the &lt;users/&gt; section as
the client will be assigned role solely based on the group he is found to belong to.
Note that this does not allow for a client to be part of multiple groups.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="user-extractor" type="rbcs:X500NameExtractorType" minOccurs="0">
<xs:annotation>
<xs:documentation>
A regex based extractor that will be used to assign a user to a connected client,
based on the X.500 name of the subject field in the client's TLS certificate.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:complexType name="X500NameExtractorType"> <xs:complexType name="X500NameExtractorType">
<xs:attribute name="attribute-name" type="xs:token"/> <xs:annotation>
<xs:attribute name="pattern" type="xs:token"/> <xs:documentation>
Extract informations from a client TLS certificates using
regular expressions applied to the X.500 name "Subject" field
</xs:documentation>
</xs:annotation>
<xs:attribute name="attribute-name" type="xs:token">
<xs:annotation>
<xs:documentation>
X.500 name attribute to apply the regex
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="pattern" type="xs:token">
<xs:annotation>
<xs:documentation>
Regex that wil be applied to the attribute value,
use regex groups to extract relevant data
(note that only the first group that appears in the regex is used)
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="authorizationType"> <xs:complexType name="authorizationType">
<xs:all> <xs:all>
<xs:element name="users" type="rbcs:usersType"/> <xs:element name="users" type="rbcs:usersType">
<xs:annotation>
<xs:documentation>
List of users registered in the application
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="groups" type="rbcs:groupsType"> <xs:element name="groups" type="rbcs:groupsType">
<xs:annotation>
<xs:documentation>
List of user groups registered in the application
</xs:documentation>
</xs:annotation>
<xs:unique name="groupKey"> <xs:unique name="groupKey">
<xs:selector xpath="group"/> <xs:selector xpath="group"/>
<xs:field xpath="@name"/> <xs:field xpath="@name"/>
@@ -99,35 +316,127 @@
</xs:complexType> </xs:complexType>
<xs:complexType name="authenticationType"> <xs:complexType name="authenticationType">
<xs:annotation>
<xs:documentation>
Authentication mechanism to assign usernames and groups to clients
</xs:documentation>
</xs:annotation>
<xs:choice> <xs:choice>
<xs:element name="basic"/> <xs:element name="basic">
<xs:element name="client-certificate" type="rbcs:tlsCertificateAuthorizationType"/> <xs:annotation>
<xs:element name="none"/> <xs:documentation>
Enable HTTP basic authentication
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="client-certificate" type="rbcs:tlsCertificateAuthorizationType">
<xs:annotation>
<xs:documentation>
Enable TLS certificate authentication
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="none">
<xs:annotation>
<xs:documentation>
Disable authentication altogether
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:choice> </xs:choice>
</xs:complexType> </xs:complexType>
<xs:complexType name="quotaType"> <xs:complexType name="quotaType">
<xs:attribute name="calls" type="xs:positiveInteger" use="required"/> <xs:annotation>
<xs:attribute name="period" type="xs:duration" use="required"/> <xs:documentation>
<xs:attribute name="max-available-calls" type="xs:positiveInteger" use="optional"/> Defines a quota for a user or a group
<xs:attribute name="initial-available-calls" type="xs:unsignedInt" use="optional"/> </xs:documentation>
</xs:annotation>
<xs:attribute name="calls" type="xs:positiveInteger" use="required">
<xs:annotation>
<xs:documentation>
Maximum number of allowed calls in a given period
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="period" type="xs:duration" use="required">
<xs:annotation>
<xs:documentation>
The period length
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="max-available-calls" type="xs:positiveInteger" use="optional">
<xs:annotation>
<xs:documentation>
Maximum number of available calls that can be accumulated
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="initial-available-calls" type="xs:unsignedInt" use="optional">
<xs:annotation>
<xs:documentation>
Number of available calls for users at their first call
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="anonymousUserType"> <xs:complexType name="anonymousUserType">
<xs:annotation>
<xs:documentation>
Placeholder for a client that is not authenticated
</xs:documentation>
</xs:annotation>
<xs:sequence> <xs:sequence>
<xs:element name="quota" type="rbcs:quotaType" minOccurs="0" maxOccurs="1"/> <xs:element name="quota" type="rbcs:quotaType" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Calls quota for the user
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:complexType name="userType"> <xs:complexType name="userType">
<xs:annotation>
<xs:documentation>
An authenticated user
</xs:documentation>
</xs:annotation>
<xs:sequence> <xs:sequence>
<xs:element name="quota" type="rbcs:quotaType" minOccurs="0" maxOccurs="1"/> <xs:element name="quota" type="rbcs:quotaType" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Calls quota for the user
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence> </xs:sequence>
<xs:attribute name="name" type="xs:token" use="required"/> <xs:attribute name="name" type="xs:token" use="required">
<xs:attribute name="password" type="xs:string" use="optional"/> <xs:annotation>
<xs:documentation>
User's name
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="password" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>
User's password used in HTTP basic authentication
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="usersType"> <xs:complexType name="usersType">
<xs:annotation>
<xs:documentation>
List of registered users, add an &lt;anonymous&gt; tag to enable authenticated user access
when authentication is enabled
</xs:documentation>
</xs:annotation>
<xs:sequence> <xs:sequence>
<xs:element name="user" type="rbcs:userType" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="user" type="rbcs:userType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="anonymous" type="rbcs:anonymousUserType" minOccurs="0" maxOccurs="1"/> <xs:element name="anonymous" type="rbcs:anonymousUserType" minOccurs="0" maxOccurs="1"/>
@@ -135,12 +444,22 @@
</xs:complexType> </xs:complexType>
<xs:complexType name="groupsType"> <xs:complexType name="groupsType">
<xs:annotation>
<xs:documentation>
List of registered user groups
</xs:documentation>
</xs:annotation>
<xs:sequence> <xs:sequence>
<xs:element name="group" type="rbcs:groupType" maxOccurs="unbounded" minOccurs="0"/> <xs:element name="group" type="rbcs:groupType" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:complexType name="groupType"> <xs:complexType name="groupType">
<xs:annotation>
<xs:documentation>
The definition of a user group, with the list of its member users
</xs:documentation>
</xs:annotation>
<xs:sequence> <xs:sequence>
<xs:element name="users" type="rbcs:userRefsType" maxOccurs="1" minOccurs="0"> <xs:element name="users" type="rbcs:userRefsType" maxOccurs="1" minOccurs="0">
<xs:unique name="userRefWriterKey"> <xs:unique name="userRefWriterKey">
@@ -148,11 +467,35 @@
<xs:field xpath="@ref"/> <xs:field xpath="@ref"/>
</xs:unique> </xs:unique>
</xs:element> </xs:element>
<xs:element name="roles" type="rbcs:rolesType" maxOccurs="1" minOccurs="0"/> <xs:element name="roles" type="rbcs:rolesType" maxOccurs="1" minOccurs="0">
<xs:element name="user-quota" type="rbcs:quotaType" minOccurs="0" maxOccurs="1"/> <xs:annotation>
<xs:element name="group-quota" type="rbcs:quotaType" minOccurs="0" maxOccurs="1"/> <xs:documentation>
The list of application roles awarded to all the members of this group
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="user-quota" type="rbcs:quotaType" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
The call quota for each user in this group
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="group-quota" type="rbcs:quotaType" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
The cumulative call quota for all users in this group
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence> </xs:sequence>
<xs:attribute name="name" type="xs:token"/> <xs:attribute name="name" type="xs:token">
<xs:annotation>
<xs:documentation>
The group's name
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:simpleType name="role" final="restriction" > <xs:simpleType name="role" final="restriction" >
@@ -172,6 +515,11 @@
</xs:complexType> </xs:complexType>
<xs:complexType name="userRefsType"> <xs:complexType name="userRefsType">
<xs:annotation>
<xs:documentation>
A list of references to users in the &lt;users&gt; section
</xs:documentation>
</xs:annotation>
<xs:sequence> <xs:sequence>
<xs:element name="user" type="rbcs:userRefType" maxOccurs="unbounded" minOccurs="0"/> <xs:element name="user" type="rbcs:userRefType" maxOccurs="unbounded" minOccurs="0"/>
<xs:element name="anonymous" minOccurs="0" maxOccurs="1"/> <xs:element name="anonymous" minOccurs="0" maxOccurs="1"/>
@@ -179,28 +527,106 @@
</xs:complexType> </xs:complexType>
<xs:complexType name="userRefType"> <xs:complexType name="userRefType">
<xs:attribute name="ref" type="xs:string" use="required"/> <xs:annotation>
<xs:documentation>
A reference to a user in the &lt;users&gt; section
</xs:documentation>
</xs:annotation>
<xs:attribute name="ref" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
Name of the referenced user
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="tlsType"> <xs:complexType name="tlsType">
<xs:annotation>
<xs:documentation>
Enable TLS protocol
</xs:documentation>
</xs:annotation>
<xs:all> <xs:all>
<xs:element name="keystore" type="rbcs:keyStoreType" /> <xs:element name="keystore" type="rbcs:keyStoreType" >
<xs:element name="truststore" type="rbcs:trustStoreType" minOccurs="0"/> <xs:annotation>
<xs:documentation>
Path to the keystore file that contains the server's key and certificate
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="truststore" type="rbcs:trustStoreType" minOccurs="0">
<xs:annotation>
<xs:documentation>
Path to the truststore file that contains the trusted CAs
for TLS client certificate verification
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all> </xs:all>
</xs:complexType> </xs:complexType>
<xs:complexType name="keyStoreType"> <xs:complexType name="keyStoreType">
<xs:attribute name="file" type="xs:string" use="required"/> <xs:attribute name="file" type="xs:string" use="required">
<xs:attribute name="password" type="xs:string"/> <xs:annotation>
<xs:attribute name="key-alias" type="xs:string" use="required"/> <xs:documentation>
<xs:attribute name="key-password" type="xs:string"/> System path to the keystore file
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="password" type="xs:string">
<xs:annotation>
<xs:documentation>
Password to open they keystore file
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="key-alias" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
Alias of the keystore entry containing the private key
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="key-password" type="xs:string">
<xs:annotation>
<xs:documentation>
Private key entry's encryption password
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="trustStoreType"> <xs:complexType name="trustStoreType">
<xs:attribute name="file" type="xs:string" use="required"/> <xs:attribute name="file" type="xs:string" use="required">
<xs:attribute name="password" type="xs:string"/> <xs:annotation>
<xs:attribute name="check-certificate-status" type="xs:boolean"/> <xs:documentation>
<xs:attribute name="require-client-certificate" type="xs:boolean" use="optional" default="false"/> Path to the trustore file
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="password" type="xs:string">
<xs:annotation>
<xs:documentation>
Trustore file password
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="check-certificate-status" type="xs:boolean">
<xs:annotation>
<xs:documentation>
Whether or not check the certificate validity using CRL/OCSP
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="require-client-certificate" type="xs:boolean" use="optional" default="false">
<xs:annotation>
<xs:documentation>
If true, the server requires a TLS client certificate from the client and simply refuses to connect
when a client certificate isn't provided
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="propertiesType"> <xs:complexType name="propertiesType">

View File

@@ -38,8 +38,6 @@ abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
serverPath, serverPath,
Configuration.EventExecutor(false), Configuration.EventExecutor(false),
Configuration.Connection( Configuration.Connection(
Duration.of(10, ChronoUnit.SECONDS),
Duration.of(10, ChronoUnit.SECONDS),
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),

View File

@@ -143,8 +143,6 @@ abstract class AbstractTlsServerTest : AbstractServerTest() {
serverPath, serverPath,
Configuration.EventExecutor(false), Configuration.EventExecutor(false),
Configuration.Connection( Configuration.Connection(
Duration.of(10, ChronoUnit.SECONDS),
Duration.of(10, ChronoUnit.SECONDS),
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),

View File

@@ -38,8 +38,6 @@ class NoAuthServerTest : AbstractServerTest() {
serverPath, serverPath,
Configuration.EventExecutor(false), Configuration.EventExecutor(false),
Configuration.Connection( Configuration.Connection(
Duration.of(10, ChronoUnit.SECONDS),
Duration.of(10, ChronoUnit.SECONDS),
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),

View File

@@ -4,8 +4,6 @@
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd"> xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd">
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="22"/> <bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="22"/>
<connection <connection
write-timeout="PT25M"
read-timeout="PT20M"
read-idle-timeout="PT10M" read-idle-timeout="PT10M"
write-idle-timeout="PT11M" write-idle-timeout="PT11M"
idle-timeout="PT30M" idle-timeout="PT30M"

View File

@@ -9,9 +9,7 @@
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"/>
read-timeout="PT5M"
write-timeout="PT5M"/>
<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" chunk-size="123">
<server host="memcached" port="11211"/> <server host="memcached" port="11211"/>

View File

@@ -5,8 +5,6 @@
xs:schemaLocation="urn:net.woggioni.rbcs.server.memcache jpms://net.woggioni.rbcs.server.memcache/net/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd"> xs:schemaLocation="urn:net.woggioni.rbcs.server.memcache jpms://net.woggioni.rbcs.server.memcache/net/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd">
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="50"/> <bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="50"/>
<connection <connection
write-timeout="PT25M"
read-timeout="PT20M"
read-idle-timeout="PT10M" read-idle-timeout="PT10M"
write-idle-timeout="PT11M" write-idle-timeout="PT11M"
idle-timeout="PT30M" idle-timeout="PT30M"

View File

@@ -4,8 +4,6 @@
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd"> xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs.xsd">
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="180"/> <bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="180"/>
<connection <connection
write-timeout="PT25M"
read-timeout="PT20M"
read-idle-timeout="PT10M" read-idle-timeout="PT10M"
write-idle-timeout="PT11M" write-idle-timeout="PT11M"
idle-timeout="PT30M" idle-timeout="PT30M"