Compare commits

..

1 Commits

Author SHA1 Message Date
woggioni 3130a809e5 improved logging
CI / build (push) Successful in 2m13s
2025-12-30 22:29:14 +08:00
9 changed files with 24 additions and 197 deletions
+2
View File
@@ -9,6 +9,8 @@ jobs:
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Execute Gradle build - name: Execute Gradle build
run: ./gradlew build run: ./gradlew build
- name: Prepare Docker image build - name: Prepare Docker image build
+2
View File
@@ -9,6 +9,8 @@ jobs:
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Execute Gradle build - name: Execute Gradle build
run: ./gradlew build run: ./gradlew build
- name: Prepare Docker image build - name: Prepare Docker image build
-173
View File
@@ -1,173 +0,0 @@
# AGENTS.md — Coding Agent Guide for RBCS
## Project Overview
RBCS (Remote Build Cache Server) is a Kotlin/Java multi-module Gradle project built on Netty.
It serves as a remote build cache for Gradle and Maven. Java is used for the public API layer
(with Lombok); Kotlin is used for all implementation modules. All modules use JPMS
(`module-info.java`). The project is built on Netty with Java 25.
**Modules:** `rbcs-api`, `rbcs-common`, `rbcs-server`, `rbcs-server-memcache`, `rbcs-client`,
`rbcs-cli`, `rbcs-servlet`, `docker`
## Build Commands
```bash
./gradlew build # Full build: compile + test + assemble
./gradlew assemble # Build JARs without running tests
./gradlew compileJava compileKotlin # Compile only
./gradlew clean # Clean build outputs
./gradlew :rbcs-server:compileKotlin # Compile a single module
./gradlew -q version # Print project version
```
## Test Commands
```bash
./gradlew test # Run all tests (all modules)
./gradlew :rbcs-server:test # Run tests for a single module
./gradlew :rbcs-server:test --tests "net.woggioni.rbcs.server.test.NoAuthServerTest"
# Run a single test class
./gradlew :rbcs-server:test --tests "net.woggioni.rbcs.server.test.NoAuthServerTest.putWithNoAuthorizationHeader"
# Run a single test method
./gradlew test --tests "*TlsServer*" # Run tests matching a pattern
./gradlew :rbcs-server:jacocoTestReport # Generate code coverage (rbcs-server only)
```
**Test framework:** JUnit 5 (Jupiter). Tests are integration-style — they start real Netty
servers and use `java.net.http.HttpClient` to make HTTP requests. Netty leak detection is
set to `PARANOID` in test configurations.
**Test locations:** `src/test/kotlin/` and `src/test/java/` following standard Gradle layout.
Test resources (XML configs, logback) are in `src/test/resources/`.
## Lint / Format
No linting or formatting tools are configured (no Checkstyle, Detekt, ktlint, Spotless, or
similar). Follow the existing code style described below.
## Code Style Guidelines
### Language Split
- **Java** for `rbcs-api` (public API consumed by Java clients) with Lombok annotations
- **Kotlin** for all implementation modules
- **`module-info.java`** in every module (JPMS)
### Import Ordering (Kotlin)
Three groups separated by blank lines, each alphabetically sorted:
1. External/third-party (`io.netty.*`, `org.slf4j.*`)
2. Java standard library (`java.*`, `javax.*`)
3. Internal project (`net.woggioni.rbcs.*`)
Import aliases are rare; used only to resolve conflicts:
`import io.netty.util.concurrent.Future as NettyFuture`
### Naming Conventions
| Element | Convention | Examples |
|---------------------|-------------------------|---------------------------------------------------|
| Classes/interfaces | PascalCase | `RemoteBuildCacheServer`, `CacheHandler` |
| Abstract classes | `Abstract` prefix | `AbstractServerTest`, `AbstractNettyHttpAuthenticator` |
| Functions/methods | camelCase | `loadConfiguration()`, `sendShutdownSignal()` |
| Variables/properties| camelCase | `bucketManager`, `sslContext` |
| Constants | SCREAMING_SNAKE_CASE | `SSL_HANDLER_NAME`, `RBCS_NAMESPACE_URI` |
| Handler names | `val NAME = ...::class.java.name` in companion object |
| Packages | lowercase dot-separated | `net.woggioni.rbcs.server.throttling` |
| Enum values | PascalCase | `Role.Reader`, `Role.Writer` |
| Kotlin files | PascalCase matching primary class; lowercase for files with only top-level functions |
### Error Handling
- Custom unchecked exception hierarchy rooted at `RbcsException extends RuntimeException`
- Domain subclasses: `CacheException`, `ConfigurationException`, `ContentTooLargeException`
- Async errors propagated via `CompletableFuture.completeExceptionally()`
- Synchronous errors thrown directly: `throw IllegalArgumentException(...)`
- Kotlin null safety idioms preferred over null checks: `?.let`, `?:`, `takeIf`
- `ExceptionHandler` maps exception types to HTTP responses via exhaustive `when`
### Type Annotations
- **Kotlin:** Heavy use of type inference for local variables. Explicit types required on:
- Class properties: `private val sslContext: SslContext?`
- Non-trivial return types: `fun run(): ServerHandle`
- `lateinit var` in tests: `protected lateinit var cfg: Configuration`
- **Java:** Lombok `@Value` for immutable data classes; modern pattern matching with `instanceof`
- No `typealias` declarations are used in this project
### Async Patterns
- Primary abstraction: `CompletableFuture<T>`**no Kotlin coroutines**
- Netty event-driven callbacks (`ChannelFuture`, `GenericFutureListener`)
- Custom `AsyncCloseable` interface wraps `CompletableFuture<Void>` for async shutdown
- Retry logic uses `CompletableFuture` composition with exponential backoff + jitter
- Virtual threads used selectively (background GC, configurable event executors)
- Connection pooling via Netty `FixedChannelPool`
### Logging
- SLF4J via custom Kotlin lazy-evaluation extension functions (defined in `rbcs-common`)
- Logger creation: `private val log = createLogger<ClassName>()` (in companion object)
- Lazy log calls: `log.debug { "message with ${interpolation}" }` (lambda only evaluated if enabled)
- Channel-aware variants: `log.debug(ctx) { "message" }` (adds MDC: channel-id, remote-address)
- Java classes use Lombok `@Slf4j`
- String templates with `${}` for Kotlin log messages
### Kotlin Idioms
- `apply` as builder pattern: `ServerBootstrap().apply { group(...); option(...) }`
- `also` for side effects, `let` for transformation, `run` for scoping
- Trailing commas in constructor parameter lists and multi-line function calls
- `sealed class`/`sealed interface` for algebraic types (e.g., `OperationOutcome`, `Authentication`)
- `data class` for value types; `companion object` for static members and constants
- No trailing semicolons
### Java Idioms (API module)
- Lombok annotations: `@Value`, `@Getter`, `@RequiredArgsConstructor`, `@EqualsAndHashCode.Include`
- `sealed interface` with `final` permitted subtypes (e.g., `CacheMessage`)
- `@FunctionalInterface` on single-method interfaces
- JPMS `module-info.java` in every module with `requires`, `exports`, `uses`, `provides`
### Testing Patterns
- `@TestInstance(Lifecycle.PER_CLASS)` — single instance per test class
- `@TestMethodOrder(MethodOrderer.OrderAnnotation)` with `@Order(n)` for sequential execution
- Abstract base class hierarchy: `AbstractServerTest``AbstractBasicAuthServerTest` → concrete
- Server lifecycle in `@BeforeAll` / `@AfterAll` (start/stop real Netty server)
- `@TempDir` for temporary directories
- `@ParameterizedTest` with `@ValueSource` and `@ArgumentsSource` for parameterized tests
- Assertions via `org.junit.jupiter.api.Assertions` (`assertEquals`, `assertArrayEquals`)
- No mocking framework — all tests are integration-style against real servers
### Documentation
Minimal doc comments in the codebase. Inline comments used sparingly for clarification.
When adding new public API, follow existing style — doc comments are not enforced but
are welcome on complex logic.
## Module Dependency Graph
```
rbcs-api → (standalone, Lombok, Netty types)
rbcs-common → Netty, Kotlin stdlib
rbcs-server → rbcs-api, rbcs-common
rbcs-server-memcache → rbcs-api, rbcs-common
rbcs-client → rbcs-api, rbcs-common
rbcs-cli → rbcs-client, rbcs-server (Picocli for CLI)
rbcs-servlet → rbcs-api, rbcs-common (Jakarta Servlet/CDI)
docker → rbcs-cli, rbcs-server-memcache
```
## Plugin System
Cache backends use the `ServiceLoader` pattern via JPMS `provides`/`uses` directives.
To add a new cache provider, implement `CacheProvider<T extends Configuration.Cache>` and
register it in your module's `module-info.java`.
## Configuration
- XML-based with XSD schema validation
- Schemas in `src/main/resources/.../schema/*.xsd`
- Default config loaded from JPMS resources: `jpms://net.woggioni.rbcs.server/...`
+3 -3
View File
@@ -28,7 +28,7 @@ allprojects { subproject ->
mavenCentral() mavenCentral()
} }
pluginManager.withPlugin('java') { pluginManager.withPlugin('java-library') {
ext { ext {
jpmsModuleName = subproject.group + '.' + subproject.name.replace('-', '.') jpmsModuleName = subproject.group + '.' + subproject.name.replace('-', '.')
@@ -55,7 +55,7 @@ allprojects { subproject ->
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
modularity.inferModulePath = true modularity.inferModulePath = true
options.release = 25 options.release = 21
} }
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
@@ -80,7 +80,7 @@ allprojects { subproject ->
pluginManager.withPlugin(catalog.plugins.kotlin.jvm.get().pluginId) { pluginManager.withPlugin(catalog.plugins.kotlin.jvm.get().pluginId) {
tasks.withType(KotlinCompile.class) { tasks.withType(KotlinCompile.class) {
compilerOptions.jvmTarget = JvmTarget.JVM_25 compilerOptions.jvmTarget = JvmTarget.JVM_21
} }
} }
+1 -19
View File
@@ -14,21 +14,8 @@ Configures server socket settings.
**Attributes:** **Attributes:**
- `host` (required): Server bind address - `host` (required): Server bind address
- `port` (required): Server port number - `port` (required): Server port number
- `proxy-protocol` (optional, default: false): Enable [HAProxy proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support. When enabled, the server decodes proxy protocol headers to extract the real client IP address from proxied connections.
- `incoming-connections-backlog-size` (optional, default: 1024): Maximum queue length for incoming connection indications - `incoming-connections-backlog-size` (optional, default: 1024): Maximum queue length for incoming connection indications
**Child Elements:**
##### `<trusted-proxies>`
Restricts which proxy servers are trusted to provide accurate client IP information via the proxy protocol. Only used when `proxy-protocol` is set to `true`.
If omitted or empty, all proxies are trusted. When specified, only connections originating from the listed CIDR ranges will have their forwarded client IP honored.
- Contains `<allow>` elements:
**Attributes:**
- `cidr` (required): An IPv4 or IPv6 CIDR range identifying a trusted proxy address (e.g. `192.168.0.0/24`, `::1/128`)
#### `<connection>` #### `<connection>`
Configures connection handling parameters. Configures connection handling parameters.
@@ -140,12 +127,7 @@ Configures TLS encryption.
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-server.xsd" xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"
> >
<bind host="0.0.0.0" port="8080" incoming-connections-backlog-size="1024" proxy-protocol="true"> <bind host="0.0.0.0" port="8080" incoming-connections-backlog-size="1024"/>
<trusted-proxies>
<allow cidr="192.168.0.11/32"/>
<allow cidr="::1/128"/>
</trusted-proxies>
</bind>
<connection <connection
max-request-size="67108864" max-request-size="67108864"
idle-timeout="PT10S" idle-timeout="PT10S"
+1 -1
View File
@@ -4,7 +4,7 @@ org.gradle.caching=true
rbcs.version = 0.3.7 rbcs.version = 0.3.7
lys.version = 2026.02.19 lys.version = 2025.12.27
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
docker.registry.url=gitea.woggioni.net docker.registry.url=gitea.woggioni.net
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
+14
View File
@@ -1,5 +1,16 @@
pluginManagement { pluginManagement {
repositories { repositories {
// mavenLocal {
// content {
// includeGroup 'net.woggioni.gradle'
// includeGroup 'net.woggioni.gradle.jpms-check'
// includeGroup 'net.woggioni.gradle.lombok'
// includeGroup 'net.woggioni.gradle.jdeps'
// includeGroup 'net.woggioni.gradle.sambal'
// includeGroup 'net.woggioni.gradle.graalvm.jlink'
// includeGroup 'net.woggioni.gradle.graalvm.native-image'
// }
// }
maven { maven {
url = getProperty('gitea.maven.url') url = getProperty('gitea.maven.url')
} }
@@ -19,6 +30,8 @@ dependencyResolutionManagement {
versionCatalogs { versionCatalogs {
catalog { catalog {
from group: 'com.lys', name: 'lys-catalog', version: getProperty('lys.version') from group: 'com.lys', name: 'lys-catalog', version: getProperty('lys.version')
// version('my-gradle-plugins', '2025.04.16')
// version('junit', '5.12.0')
} }
} }
} }
@@ -33,3 +46,4 @@ include 'rbcs-client'
include 'rbcs-server' include 'rbcs-server'
include 'rbcs-servlet' include 'rbcs-servlet'
include 'docker' include 'docker'
//include 'bug'