Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
aeae98d9eb
|
|||
6cba4d24bb
|
|||
52a1b4c200
|
|||
559ad5e528
|
|||
fd0bd1ee5f
|
|||
0e92998f16
|
|||
9eef91ebba
|
|||
3416c327b9
|
|||
9bdaa0d32e
|
|||
206bcd6319
|
|||
3774ab8ef0
|
|||
303828392e
|
|||
5d8cbe34ef
|
|||
85c0d4a384
|
|||
ae8817ad2a
|
|||
69f215e68f
|
|||
222b475223
|
|||
ede515e2ca
|
|||
974fdb7a91
|
|||
a294229ff0
|
|||
9600dd7e4f
|
|||
729276a2b1
|
|||
7ba7070693
|
|||
59a12d6218
|
|||
fc298de548
|
|||
8b639fc0b3
|
|||
5545f618f9
|
|||
43c0938d9a
|
80
.gitea/workflows/build-dev.yaml
Normal file
80
.gitea/workflows/build-dev.yaml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: woryzen
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
- name: Execute Gradle build
|
||||||
|
run: ./gradlew build
|
||||||
|
- name: Prepare Docker image build
|
||||||
|
run: ./gradlew prepareDockerBuild
|
||||||
|
- name: Get project version
|
||||||
|
id: retrieve-version
|
||||||
|
run: ./gradlew -q version >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
driver: docker-container
|
||||||
|
- name: Login to Gitea container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: gitea.woggioni.net
|
||||||
|
username: woggioni
|
||||||
|
password: ${{ secrets.PUBLISHER_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Build rbcs Docker image
|
||||||
|
uses: docker/build-push-action@v5.3.0
|
||||||
|
with:
|
||||||
|
context: "docker/build/docker"
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
pull: true
|
||||||
|
tags: |
|
||||||
|
gitea.woggioni.net/woggioni/rbcs:vanilla-dev
|
||||||
|
target: release-vanilla
|
||||||
|
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
||||||
|
-
|
||||||
|
name: Build rbcs memcache Docker image
|
||||||
|
uses: docker/build-push-action@v5.3.0
|
||||||
|
with:
|
||||||
|
context: "docker/build/docker"
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
pull: true
|
||||||
|
tags: |
|
||||||
|
gitea.woggioni.net/woggioni/rbcs:memcache-dev
|
||||||
|
target: release-memcache
|
||||||
|
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
||||||
|
cache-to: type=registry,mode=max,compression=zstd,image-manifest=true,oci-mediatypes=true,ref=gitea.woggioni.net/woggioni/rbcs:buildx
|
||||||
|
-
|
||||||
|
name: Build rbcs native Docker image
|
||||||
|
uses: docker/build-push-action@v5.3.0
|
||||||
|
with:
|
||||||
|
context: "docker/build/docker"
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
pull: true
|
||||||
|
tags: |
|
||||||
|
gitea.woggioni.net/woggioni/rbcs:native-dev
|
||||||
|
target: release-native
|
||||||
|
-
|
||||||
|
name: Build rbcs jlink Docker image
|
||||||
|
uses: docker/build-push-action@v5.3.0
|
||||||
|
with:
|
||||||
|
context: "docker/build/docker"
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
pull: true
|
||||||
|
tags: |
|
||||||
|
gitea.woggioni.net/woggioni/rbcs:jlink-dev
|
||||||
|
target: release-jlink
|
||||||
|
|
@@ -5,7 +5,7 @@ on:
|
|||||||
- '*'
|
- '*'
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: hostinger
|
runs-on: woryzen
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -52,6 +52,8 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
pull: true
|
pull: true
|
||||||
tags: |
|
tags: |
|
||||||
|
gitea.woggioni.net/woggioni/rbcs:latest
|
||||||
|
gitea.woggioni.net/woggioni/rbcs:${{ steps.retrieve-version.outputs.VERSION }}
|
||||||
gitea.woggioni.net/woggioni/rbcs:memcache
|
gitea.woggioni.net/woggioni/rbcs:memcache
|
||||||
gitea.woggioni.net/woggioni/rbcs:memcache-${{ steps.retrieve-version.outputs.VERSION }}
|
gitea.woggioni.net/woggioni/rbcs:memcache-${{ steps.retrieve-version.outputs.VERSION }}
|
||||||
target: release-memcache
|
target: release-memcache
|
||||||
@@ -66,11 +68,21 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
pull: true
|
pull: true
|
||||||
tags: |
|
tags: |
|
||||||
gitea.woggioni.net/woggioni/rbcs:latest
|
|
||||||
gitea.woggioni.net/woggioni/rbcs:${{ steps.retrieve-version.outputs.VERSION }}
|
|
||||||
gitea.woggioni.net/woggioni/rbcs:native
|
gitea.woggioni.net/woggioni/rbcs:native
|
||||||
gitea.woggioni.net/woggioni/rbcs:native-${{ steps.retrieve-version.outputs.VERSION }}
|
gitea.woggioni.net/woggioni/rbcs:native-${{ steps.retrieve-version.outputs.VERSION }}
|
||||||
target: release-native
|
target: release-native
|
||||||
|
-
|
||||||
|
name: Build rbcs jlink Docker image
|
||||||
|
uses: docker/build-push-action@v5.3.0
|
||||||
|
with:
|
||||||
|
context: "docker/build/docker"
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
pull: true
|
||||||
|
tags: |
|
||||||
|
gitea.woggioni.net/woggioni/rbcs:jlink
|
||||||
|
gitea.woggioni.net/woggioni/rbcs:jlink-${{ steps.retrieve-version.outputs.VERSION }}-jlink
|
||||||
|
target: release-jlink
|
||||||
- name: Publish artifacts
|
- name: Publish artifacts
|
||||||
env:
|
env:
|
||||||
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
|
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
|
||||||
|
@@ -82,6 +82,10 @@ It behaves the same as the jar file but it doesn't require a JVM and it has fast
|
|||||||
because of GraalVM's [closed-world assumption](https://www.graalvm.org/latest/reference-manual/native-image/basics/#static-analysis),
|
because of GraalVM's [closed-world assumption](https://www.graalvm.org/latest/reference-manual/native-image/basics/#static-analysis),
|
||||||
the native executable does not supports plugins, so it comes with all plugins embedded into it.
|
the native executable does not supports plugins, so it comes with all plugins embedded into it.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> The native executable is built with `-march=skylake`, so it may fail with SIGILL on x86 CPUs that do not support
|
||||||
|
> the full skylake instruction set (as a rule of thumb, older than 2015)
|
||||||
|
|
||||||
## Integration with build tools
|
## Integration with build tools
|
||||||
|
|
||||||
### Use RBCS with Gradle
|
### Use RBCS with Gradle
|
||||||
|
94
benchmark/rbcs-filesystem.yml
Normal file
94
benchmark/rbcs-filesystem.yml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: rbcs-server
|
||||||
|
data:
|
||||||
|
rbcs-server.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-server.xsd"
|
||||||
|
>
|
||||||
|
<bind host="0.0.0.0" port="8080" incoming-connections-backlog-size="128"/>
|
||||||
|
<connection
|
||||||
|
max-request-size="0xd000000"
|
||||||
|
idle-timeout="PT15S"
|
||||||
|
read-idle-timeout="PT30S"
|
||||||
|
write-idle-timeout="PT30S"/>
|
||||||
|
<event-executor use-virtual-threads="true"/>
|
||||||
|
<cache xs:type="rbcs:fileSystemCacheType" max-age="P7D" enable-compression="false" path="/home/luser/cache" digest="SHA-224"/>
|
||||||
|
</rbcs:server>
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: rbcs-pvc
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 16Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: rbcs-deployment
|
||||||
|
labels:
|
||||||
|
app: rbcs
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: rbcs
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: rbcs
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: rbcs
|
||||||
|
image: gitea.woggioni.net/woggioni/rbcs:memcache
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command: ["java", "-Dlogback.configurationFile=logback.xml", "-XX:MaxRAMPercentage=75","-jar", "/home/luser/rbcs.jar"]
|
||||||
|
args: ['server', '-c', 'rbcs-server.xml']
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
volumeMounts:
|
||||||
|
- name: config-volume
|
||||||
|
mountPath: /home/luser/rbcs-server.xml
|
||||||
|
subPath: rbcs-server.xml
|
||||||
|
- name: cache-volume
|
||||||
|
mountPath: /home/luser/cache
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "0.25Gi"
|
||||||
|
cpu: "1"
|
||||||
|
limits:
|
||||||
|
memory: "0.5Gi"
|
||||||
|
cpu: "1"
|
||||||
|
volumes:
|
||||||
|
- name: config-volume
|
||||||
|
configMap:
|
||||||
|
name: rbcs-server
|
||||||
|
- name: cache-volume
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: rbcs-pvc
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: rbcs-service
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app: rbcs
|
||||||
|
|
77
benchmark/rbcs-in-memory.yml
Normal file
77
benchmark/rbcs-in-memory.yml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: rbcs-server
|
||||||
|
data:
|
||||||
|
rbcs-server.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-server.xsd"
|
||||||
|
>
|
||||||
|
<bind host="0.0.0.0" port="8080" incoming-connections-backlog-size="128"/>
|
||||||
|
<connection
|
||||||
|
max-request-size="0xd000000"
|
||||||
|
idle-timeout="PT15S"
|
||||||
|
read-idle-timeout="PT30S"
|
||||||
|
write-idle-timeout="PT30S"/>
|
||||||
|
<event-executor use-virtual-threads="true"/>
|
||||||
|
<cache xs:type="rbcs:inMemoryCacheType" max-age="P7D" enable-compression="false" max-size="0x40000000" digest="SHA-224"/>
|
||||||
|
</rbcs:server>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: rbcs-deployment
|
||||||
|
labels:
|
||||||
|
app: rbcs
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: rbcs
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: rbcs
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: rbcs
|
||||||
|
image: gitea.woggioni.net/woggioni/rbcs:memcache
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command: ["java", "-Dlogback.configurationFile=logback.xml", "-XX:MaxRAMPercentage=75","-jar", "/home/luser/rbcs.jar"]
|
||||||
|
args: ['server', '-c', 'rbcs-server.xml']
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
volumeMounts:
|
||||||
|
- name: config-volume
|
||||||
|
mountPath: /home/luser/rbcs-server.xml
|
||||||
|
subPath: rbcs-server.xml
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "0.5Gi"
|
||||||
|
cpu: "1"
|
||||||
|
limits:
|
||||||
|
memory: "4Gi"
|
||||||
|
cpu: "1"
|
||||||
|
volumes:
|
||||||
|
- name: config-volume
|
||||||
|
configMap:
|
||||||
|
name: rbcs-server
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: rbcs-service
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app: rbcs
|
||||||
|
|
118
benchmark/rbcs-memcache.yml
Normal file
118
benchmark/rbcs-memcache.yml
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: rbcs-server
|
||||||
|
data:
|
||||||
|
rbcs-server.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-server.xsd"
|
||||||
|
>
|
||||||
|
<bind host="0.0.0.0" port="8080" incoming-connections-backlog-size="128"/>
|
||||||
|
<connection
|
||||||
|
max-request-size="0xd000000"
|
||||||
|
idle-timeout="PT15S"
|
||||||
|
read-idle-timeout="PT30S"
|
||||||
|
write-idle-timeout="PT30S"/>
|
||||||
|
<event-executor use-virtual-threads="true"/>
|
||||||
|
<!--cache xs:type="rbcs:inMemoryCacheType" max-age="P7D" enable-compression="false" max-size="0x10000000" /-->
|
||||||
|
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" chunk-size="0x1000" digest="SHA-224">
|
||||||
|
<server host="memcached-service" port="11211" max-connections="256"/>
|
||||||
|
</cache>
|
||||||
|
</rbcs:server>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: rbcs-deployment
|
||||||
|
labels:
|
||||||
|
app: rbcs
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: rbcs
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: rbcs
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: rbcs
|
||||||
|
image: gitea.woggioni.net/woggioni/rbcs:memcache
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command: ["java", "-Dlogback.configurationFile=logback.xml", "-XX:MaxRAMPercentage=75","-jar", "/home/luser/rbcs.jar"]
|
||||||
|
args: ['server', '-c', 'rbcs-server.xml']
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
volumeMounts:
|
||||||
|
- name: config-volume
|
||||||
|
mountPath: /home/luser/rbcs-server.xml
|
||||||
|
subPath: rbcs-server.xml
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "0.5Gi"
|
||||||
|
cpu: "1"
|
||||||
|
limits:
|
||||||
|
memory: "0.5Gi"
|
||||||
|
cpu: "3.5"
|
||||||
|
volumes:
|
||||||
|
- name: config-volume
|
||||||
|
configMap:
|
||||||
|
name: rbcs-server
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: rbcs-service
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app: rbcs
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: memcached-deployment
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: memcached
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: memcached
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: memcached
|
||||||
|
image: memcached
|
||||||
|
args: ["-I", "128m", "-m", "4096", "-t", "1"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "1Gi"
|
||||||
|
cpu: "500m" # 0.5 CPU
|
||||||
|
limits:
|
||||||
|
memory: "5Gi"
|
||||||
|
cpu: "500m" # 0.5 CP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: memcached-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP # ClusterIP makes it accessible only within the cluster
|
||||||
|
ports:
|
||||||
|
- port: 11211 # Default memcached port
|
||||||
|
targetPort: 11211
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app: memcached
|
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,22 +16,22 @@ All test were executed under the following conditions:
|
|||||||
|
|
||||||
| Cache backend | CPU | CPU quota | Memory quota (GB) | Request size (b) | Client connections | PUT (req/s) | GET (req/s) |
|
| Cache backend | CPU | CPU quota | Memory quota (GB) | Request size (b) | Client connections | PUT (req/s) | GET (req/s) |
|
||||||
|----------------|---------------------|-----------|-------------------|------------------|--------------------|-------------|-------------|
|
|----------------|---------------------|-----------|-------------------|------------------|--------------------|-------------|-------------|
|
||||||
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 128 | 10 | 3691 | 4037 |
|
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 128 | 10 | 7867 | 13762 |
|
||||||
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 128 | 100 | 6881 | 7483 |
|
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 128 | 100 | 7728 | 14180 |
|
||||||
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 512 | 10 | 3790 | 4069 |
|
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 512 | 10 | 7964 | 10992 |
|
||||||
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 512 | 100 | 6716 | 7408 |
|
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 512 | 100 | 8415 | 12478 |
|
||||||
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 4096 | 10 | 3399 | 1974 |
|
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 4096 | 10 | 4268 | 5395 |
|
||||||
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 4096 | 100 | 5341 | 6402 |
|
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 4096 | 100 | 5585 | 8259 |
|
||||||
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 65536 | 10 | 1099 | 1116 |
|
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 65536 | 10 | 1063 | 1185 |
|
||||||
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 65536 | 100 | 1379 | 1703 |
|
| in-memory | Intel Celeron J3455 | 1.00 | 4 | 65536 | 100 | 1522 | 1366 |
|
||||||
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 128 | 10 | 4443 | 5170 |
|
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 128 | 10 | 11271 | 14092 |
|
||||||
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 128 | 100 | 12813 | 13568 |
|
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 128 | 100 | 16064 | 24201 |
|
||||||
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 512 | 10 | 4450 | 4383 |
|
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 512 | 10 | 11504 | 13077 |
|
||||||
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 512 | 100 | 12212 | 13586 |
|
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 512 | 100 | 17379 | 22094 |
|
||||||
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 4096 | 10 | 3441 | 3012 |
|
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 4096 | 10 | 9151 | 9489 |
|
||||||
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 4096 | 100 | 8982 | 10452 |
|
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 4096 | 100 | 13194 | 18268 |
|
||||||
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 65536 | 10 | 1391 | 1167 |
|
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 65536 | 10 | 1590 | 1174 |
|
||||||
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 65536 | 100 | 1303 | 1151 |
|
| in-memory | Intel Celeron J3455 | 3.50 | 4 | 65536 | 100 | 1539 | 1561 |
|
||||||
|
|
||||||
### Filesystem cache backend
|
### Filesystem cache backend
|
||||||
|
|
||||||
@@ -42,23 +42,22 @@ TLS: disabled
|
|||||||
|
|
||||||
| Cache backend | CPU | CPU quota | Memory quota (GB) | Request size (b) | Client connections | PUT (req/s) | GET (req/s) |
|
| Cache backend | CPU | CPU quota | Memory quota (GB) | Request size (b) | Client connections | PUT (req/s) | GET (req/s) |
|
||||||
|---------------|---------------------|-----------|-------------------|------------------|--------------------|-------------|-------------|
|
|---------------|---------------------|-----------|-------------------|------------------|--------------------|-------------|-------------|
|
||||||
| filesystem | Intel Celeron J3455 | 1.00 | 0.25 | 128 | 10 | 1208 | 2048 |
|
| filesystem | Intel Celeron J3455 | 1.00 | 0.5 | 128 | 10 | 1478 | 5771 |
|
||||||
| filesystem | Intel Celeron J3455 | 1.00 | 0.25 | 128 | 100 | 1304 | 2394 |
|
| filesystem | Intel Celeron J3455 | 1.00 | 0.5 | 128 | 100 | 3166 | 8070 |
|
||||||
| filesystem | Intel Celeron J3455 | 1.00 | 0.25 | 512 | 10 | 1408 | 2157 |
|
| filesystem | Intel Celeron J3455 | 1.00 | 0.5 | 512 | 10 | 1717 | 5895 |
|
||||||
| filesystem | Intel Celeron J3455 | 1.00 | 0.25 | 512 | 100 | 1282 | 1888 |
|
| filesystem | Intel Celeron J3455 | 1.00 | 0.5 | 512 | 100 | 1125 | 6564 |
|
||||||
| filesystem | Intel Celeron J3455 | 1.00 | 0.25 | 4096 | 10 | 1291 | 1256 |
|
| filesystem | Intel Celeron J3455 | 1.00 | 0.5 | 4096 | 10 | 819 | 2509 |
|
||||||
| filesystem | Intel Celeron J3455 | 1.00 | 0.25 | 4096 | 100 | 1170 | 1423 |
|
| filesystem | Intel Celeron J3455 | 1.00 | 0.5 | 4096 | 100 | 1136 | 2365 |
|
||||||
| filesystem | Intel Celeron J3455 | 1.00 | 0.25 | 65536 | 10 | 313 | 606 |
|
| filesystem | Intel Celeron J3455 | 1.00 | 0.5 | 65536 | 10 | 584 | 632 |
|
||||||
| filesystem | Intel Celeron J3455 | 1.00 | 0.25 | 65536 | 100 | 298 | 609 |
|
| filesystem | Intel Celeron J3455 | 1.00 | 0.5 | 65536 | 100 | 529 | 635 |
|
||||||
| filesystem | Intel Celeron J3455 | 3.50 | 0.25 | 128 | 10 | 2195 | 3477 |
|
| filesystem | Intel Celeron J3455 | 3.50 | 0.5 | 128 | 10 | 1227 | 3342 |
|
||||||
| filesystem | Intel Celeron J3455 | 3.50 | 0.25 | 128 | 100 | 2480 | 6207 |
|
| filesystem | Intel Celeron J3455 | 3.50 | 0.5 | 128 | 100 | 1156 | 4035 |
|
||||||
| filesystem | Intel Celeron J3455 | 3.50 | 0.25 | 512 | 10 | 2164 | 3413 |
|
| filesystem | Intel Celeron J3455 | 3.50 | 0.5 | 512 | 10 | 979 | 3294 |
|
||||||
| filesystem | Intel Celeron J3455 | 3.50 | 0.25 | 512 | 100 | 2842 | 6218 |
|
| filesystem | Intel Celeron J3455 | 3.50 | 0.5 | 512 | 100 | 1217 | 3888 |
|
||||||
| filesystem | Intel Celeron J3455 | 3.50 | 0.25 | 4096 | 10 | 1302 | 2591 |
|
| filesystem | Intel Celeron J3455 | 3.50 | 0.5 | 4096 | 10 | 535 | 1805 |
|
||||||
| filesystem | Intel Celeron J3455 | 3.50 | 0.25 | 4096 | 100 | 2270 | 3045 |
|
| filesystem | Intel Celeron J3455 | 3.50 | 0.5 | 4096 | 100 | 555 | 1910 |
|
||||||
| filesystem | Intel Celeron J3455 | 3.50 | 0.25 | 65536 | 10 | 375 | 394 |
|
| filesystem | Intel Celeron J3455 | 3.50 | 0.5 | 65536 | 10 | 301 | 494 |
|
||||||
| filesystem | Intel Celeron J3455 | 3.50 | 0.25 | 65536 | 100 | 364 | 462 |
|
| filesystem | Intel Celeron J3455 | 3.50 | 0.5 | 65536 | 100 | 353 | 595 |
|
||||||
|
|
||||||
|
|
||||||
### Memcache cache backend
|
### Memcache cache backend
|
||||||
|
|
||||||
@@ -69,19 +68,19 @@ TLS: disabled
|
|||||||
|
|
||||||
| Cache backend | CPU | CPU quota | Memory quota (GB) | Request size (b) | Client connections | PUT (req/s) | GET (req/s) |
|
| Cache backend | CPU | CPU quota | Memory quota (GB) | Request size (b) | Client connections | PUT (req/s) | GET (req/s) |
|
||||||
|---------------|---------------------|-----------|-------------------|------------------|--------------------|-------------|-------------|
|
|---------------|---------------------|-----------|-------------------|------------------|--------------------|-------------|-------------|
|
||||||
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 128 | 10 | 2505 | 2578 |
|
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 128 | 10 | 3380 | 6083 |
|
||||||
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 128 | 100 | 3582 | 3935 |
|
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 128 | 100 | 3323 | 4998 |
|
||||||
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 512 | 10 | 2495 | 2784 |
|
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 512 | 10 | 3924 | 6086 |
|
||||||
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 512 | 100 | 3565 | 3883 |
|
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 512 | 100 | 3440 | 5049 |
|
||||||
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 4096 | 10 | 2174 | 2505 |
|
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 4096 | 10 | 3347 | 5255 |
|
||||||
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 4096 | 100 | 2937 | 3563 |
|
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 4096 | 100 | 3685 | 4693 |
|
||||||
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 65536 | 10 | 648 | 1074 |
|
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 65536 | 10 | 1304 | 1343 |
|
||||||
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 65536 | 100 | 724 | 1548 |
|
| memcache | Intel Celeron J3455 | 1.00 | 0.25 | 65536 | 100 | 1481 | 1541 |
|
||||||
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 128 | 10 | 2362 | 2927 |
|
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 128 | 10 | 4667 | 7984 |
|
||||||
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 128 | 100 | 5491 | 6531 |
|
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 128 | 100 | 4044 | 8358 |
|
||||||
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 512 | 10 | 2125 | 2807 |
|
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 512 | 10 | 4177 | 7828 |
|
||||||
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 512 | 100 | 5173 | 6242 |
|
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 512 | 100 | 4079 | 8794 |
|
||||||
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 4096 | 10 | 1720 | 2397 |
|
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 4096 | 10 | 4588 | 6869 |
|
||||||
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 4096 | 100 | 3871 | 5859 |
|
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 4096 | 100 | 5343 | 7797 |
|
||||||
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 65536 | 10 | 616 | 1016 |
|
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 65536 | 10 | 1624 | 1317 |
|
||||||
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 65536 | 100 | 820 | 1677 |
|
| memcache | Intel Celeron J3455 | 3.50 | 0.25 | 65536 | 100 | 1633 | 1317 |
|
||||||
|
@@ -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>
|
||||||
-->
|
-->
|
||||||
|
@@ -5,7 +5,7 @@ WORKDIR /home/luser
|
|||||||
|
|
||||||
FROM base-release AS release-vanilla
|
FROM base-release AS release-vanilla
|
||||||
ADD rbcs-cli-envelope-*.jar rbcs.jar
|
ADD rbcs-cli-envelope-*.jar rbcs.jar
|
||||||
ENTRYPOINT ["java", "-XX:+UseSerialGC", "-XX:GCTimeRatio=24", "-jar", "/home/luser/rbcs.jar", "server"]
|
ENTRYPOINT ["java", "-Dlogback.configurationFile=logback.xml", "-XX:MaxRAMPercentage=70", "-XX:GCTimeRatio=24", "-XX:+UseZGC", "-XX:+ZGenerational", "-jar", "/home/luser/rbcs.jar"]
|
||||||
|
|
||||||
FROM base-release AS release-memcache
|
FROM base-release AS release-memcache
|
||||||
ADD --chown=luser:luser rbcs-cli-envelope-*.jar rbcs.jar
|
ADD --chown=luser:luser rbcs-cli-envelope-*.jar rbcs.jar
|
||||||
@@ -14,10 +14,29 @@ WORKDIR /home/luser/plugins
|
|||||||
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-server-memcache*.tar
|
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-server-memcache*.tar
|
||||||
WORKDIR /home/luser
|
WORKDIR /home/luser
|
||||||
ADD logback.xml .
|
ADD logback.xml .
|
||||||
ENTRYPOINT ["java", "-Dlogback.configurationFile=logback.xml", "-XX:+UseSerialGC", "-XX:GCTimeRatio=24", "-jar", "/home/luser/rbcs.jar", "server"]
|
ENTRYPOINT ["java", "-Dlogback.configurationFile=logback.xml", "-XX:MaxRAMPercentage=70", "-XX:GCTimeRatio=24", "-XX:+UseZGC", "-XX:+ZGenerational", "-jar", "/home/luser/rbcs.jar"]
|
||||||
|
|
||||||
|
FROM busybox:musl AS base-native
|
||||||
|
RUN mkdir -p /var/lib/rbcs /etc/rbcs
|
||||||
|
RUN adduser -D -u 1000 rbcs -h /var/lib/rbcs
|
||||||
|
|
||||||
FROM scratch AS release-native
|
FROM scratch AS release-native
|
||||||
ADD rbcs-cli.upx /rbcs/rbcs-cli
|
COPY --from=base-native /etc/passwd /etc/passwd
|
||||||
ENV RBCS_CONFIGURATION_DIR="/rbcs"
|
COPY --from=base-native /etc/rbcs /etc/rbcs
|
||||||
WORKDIR /rbcs
|
COPY --from=base-native /var/lib/rbcs /var/lib/rbcs
|
||||||
ENTRYPOINT ["/rbcs/rbcs-cli"]
|
ADD rbcs-cli.upx /usr/bin/rbcs-cli
|
||||||
|
ENV RBCS_CONFIGURATION_DIR="/etc/rbcs"
|
||||||
|
USER rbcs
|
||||||
|
WORKDIR /var/lib/rbcs
|
||||||
|
ENTRYPOINT ["/usr/bin/rbcs-cli", "-XX:MaximumHeapSizePercent=70"]
|
||||||
|
|
||||||
|
FROM debian:12-slim AS release-jlink
|
||||||
|
RUN mkdir -p /usr/share/java/rbcs
|
||||||
|
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-cli*.tar -C /usr/share/java/rbcs
|
||||||
|
ADD --chmod=755 rbcs-cli.sh /usr/local/bin/rbcs-cli
|
||||||
|
RUN adduser -u 1000 luser
|
||||||
|
USER luser
|
||||||
|
WORKDIR /home/luser
|
||||||
|
ADD logback.xml .
|
||||||
|
ENV JAVA_OPTS=-XX:-UseJVMCICompiler\ -Dlogback.configurationFile=logback.xml\ -XX:MaxRAMPercentage=70\ -XX:GCTimeRatio=24\ -XX:+UseZGC\ -XX:+ZGenerational
|
||||||
|
ENTRYPOINT ["/usr/local/bin/rbcs-cli"]
|
||||||
|
@@ -11,11 +11,15 @@ The `memcache` image is similar to the `vanilla` image, except that it also cont
|
|||||||
the `rbcs-server-memcache` plugin in the `plugins` folder, use this image if you don't want to use the `native`
|
the `rbcs-server-memcache` plugin in the `plugins` folder, use this image if you don't want to use the `native`
|
||||||
image and want to use memcache as the cache backend
|
image and want to use memcache as the cache backend
|
||||||
|
|
||||||
The `native` image contains a native, statically-linked executable created with GraalVM
|
The `native` image contains a native, statically-linked executable created with GraalVM Native Image
|
||||||
that has no userspace dependencies. It also embeds the memcache plugin inside the executable.
|
that has no userspace dependencies. It also embeds the memcache plugin inside the executable.
|
||||||
Use this image for maximum efficiency and minimal memory footprint.
|
Use this image for maximum efficiency and minimal memory footprint.
|
||||||
|
|
||||||
## Which image shoud I use?
|
The `jlink` image contains a custom Java runtime created with GraalVM's Jlink
|
||||||
|
that only depends on glibc. It also contains the memcache plugin in the module path.
|
||||||
|
Use this image for best performance.
|
||||||
|
|
||||||
|
## Which image should I use?
|
||||||
The `native` image uses Java's SerialGC, so it's ideal for constrained environment like containers or small servers,
|
The `native` image uses Java's SerialGC, so it's ideal for constrained environment like containers or small servers,
|
||||||
if you have a lot of resources and want to squeeze out the maximum throughput you should consider the
|
if you have a lot of resources and want to squeeze out the maximum throughput you should consider the
|
||||||
`vanilla` or `memcache` image, then choose and fine tune the garbage collector.
|
`vanilla` or `memcache` image, then choose and fine tune the garbage collector.
|
||||||
|
@@ -29,7 +29,7 @@ Provider<Copy> prepareDockerBuild = tasks.register('prepareDockerBuild', Copy) {
|
|||||||
group = 'docker'
|
group = 'docker'
|
||||||
into project.layout.buildDirectory.file('docker')
|
into project.layout.buildDirectory.file('docker')
|
||||||
from(configurations.docker)
|
from(configurations.docker)
|
||||||
from(file('Dockerfile'))
|
from(files('Dockerfile', 'rbcs-cli.sh'))
|
||||||
from(rootProject.file('conf')) {
|
from(rootProject.file('conf')) {
|
||||||
include 'logback.xml'
|
include 'logback.xml'
|
||||||
}
|
}
|
||||||
|
3
docker/rbcs-cli.sh
Normal file
3
docker/rbcs-cli.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
DIR=/usr/share/java/rbcs
|
||||||
|
$DIR/bin/java $JAVA_OPTS -m net.woggioni.rbcs.cli "$@"
|
@@ -2,9 +2,9 @@ org.gradle.configuration-cache=false
|
|||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
|
||||||
rbcs.version = 0.2.0
|
rbcs.version = 0.3.1
|
||||||
|
|
||||||
lys.version = 2025.03.03
|
lys.version = 2025.06.10
|
||||||
|
|
||||||
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
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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-8.12-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
3
gradlew
vendored
3
gradlew
vendored
@@ -86,8 +86,7 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
' "$PWD" ) || exit
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
@@ -5,9 +5,12 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation catalog.slf4j.api
|
||||||
|
implementation project(':rbcs-common')
|
||||||
api catalog.netty.common
|
api catalog.netty.common
|
||||||
api catalog.netty.buffer
|
api catalog.netty.buffer
|
||||||
api catalog.netty.handler
|
api catalog.netty.handler
|
||||||
|
api catalog.netty.codec.http
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
module net.woggioni.rbcs.api {
|
module net.woggioni.rbcs.api {
|
||||||
requires static lombok;
|
requires static lombok;
|
||||||
requires java.xml;
|
|
||||||
requires io.netty.buffer;
|
|
||||||
requires io.netty.handler;
|
requires io.netty.handler;
|
||||||
requires io.netty.transport;
|
|
||||||
requires io.netty.common;
|
requires io.netty.common;
|
||||||
|
requires net.woggioni.rbcs.common;
|
||||||
|
requires io.netty.transport;
|
||||||
|
requires io.netty.codec.http;
|
||||||
|
requires io.netty.buffer;
|
||||||
|
requires org.slf4j;
|
||||||
|
requires java.xml;
|
||||||
|
|
||||||
|
|
||||||
exports net.woggioni.rbcs.api;
|
exports net.woggioni.rbcs.api;
|
||||||
exports net.woggioni.rbcs.api.exception;
|
exports net.woggioni.rbcs.api.exception;
|
||||||
exports net.woggioni.rbcs.api.message;
|
exports net.woggioni.rbcs.api.message;
|
||||||
|
@@ -0,0 +1,57 @@
|
|||||||
|
package net.woggioni.rbcs.api;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
|
import io.netty.util.ReferenceCounted;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.woggioni.rbcs.api.message.CacheMessage;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class CacheHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
private boolean requestFinished = false;
|
||||||
|
|
||||||
|
abstract protected void channelRead0(ChannelHandlerContext ctx, CacheMessage msg);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||||
|
if(!requestFinished && msg instanceof CacheMessage) {
|
||||||
|
if(msg instanceof CacheMessage.LastCacheContent) requestFinished = true;
|
||||||
|
try {
|
||||||
|
channelRead0(ctx, (CacheMessage) msg);
|
||||||
|
} finally {
|
||||||
|
if(msg instanceof ReferenceCounted rc) rc.release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendMessageAndFlush(ChannelHandlerContext ctx, Object msg) {
|
||||||
|
sendMessage(ctx, msg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendMessage(ChannelHandlerContext ctx, Object msg) {
|
||||||
|
sendMessage(ctx, msg, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(ChannelHandlerContext ctx, Object msg, boolean flush) {
|
||||||
|
ctx.write(msg);
|
||||||
|
if(
|
||||||
|
msg instanceof CacheMessage.LastCacheContent ||
|
||||||
|
msg instanceof CacheMessage.CachePutResponse ||
|
||||||
|
msg instanceof CacheMessage.CacheValueNotFoundResponse ||
|
||||||
|
msg instanceof LastHttpContent
|
||||||
|
) {
|
||||||
|
ctx.flush();
|
||||||
|
ctx.pipeline().remove(this);
|
||||||
|
} else if(flush) {
|
||||||
|
ctx.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
super.exceptionCaught(ctx, cause);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +1,13 @@
|
|||||||
package net.woggioni.rbcs.api;
|
package net.woggioni.rbcs.api;
|
||||||
|
|
||||||
import io.netty.channel.ChannelFactory;
|
import io.netty.channel.ChannelFactory;
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
import io.netty.channel.socket.DatagramChannel;
|
import io.netty.channel.socket.DatagramChannel;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
|
||||||
public interface CacheHandlerFactory extends AsyncCloseable {
|
public interface CacheHandlerFactory extends AsyncCloseable {
|
||||||
ChannelHandler newHandler(
|
CacheHandler newHandler(
|
||||||
|
Configuration configuration,
|
||||||
EventLoopGroup eventLoopGroup,
|
EventLoopGroup eventLoopGroup,
|
||||||
ChannelFactory<SocketChannel> socketChannelFactory,
|
ChannelFactory<SocketChannel> socketChannelFactory,
|
||||||
ChannelFactory<DatagramChannel> datagramChannelFactory
|
ChannelFactory<DatagramChannel> datagramChannelFactory
|
||||||
|
@@ -21,6 +21,8 @@ public class Configuration {
|
|||||||
@NonNull
|
@NonNull
|
||||||
EventExecutor eventExecutor;
|
EventExecutor eventExecutor;
|
||||||
@NonNull
|
@NonNull
|
||||||
|
RateLimiter rateLimiter;
|
||||||
|
@NonNull
|
||||||
Connection connection;
|
Connection connection;
|
||||||
Map<String, User> users;
|
Map<String, User> users;
|
||||||
Map<String, Group> groups;
|
Map<String, Group> groups;
|
||||||
@@ -28,6 +30,13 @@ public class Configuration {
|
|||||||
Authentication authentication;
|
Authentication authentication;
|
||||||
Tls tls;
|
Tls tls;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public static class RateLimiter {
|
||||||
|
boolean delayRequest;
|
||||||
|
int messageBufferSize;
|
||||||
|
int maxQueuedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public static class EventExecutor {
|
public static class EventExecutor {
|
||||||
boolean useVirtualThreads;
|
boolean useVirtualThreads;
|
||||||
@@ -39,6 +48,7 @@ public class Configuration {
|
|||||||
Duration readIdleTimeout;
|
Duration readIdleTimeout;
|
||||||
Duration writeIdleTimeout;
|
Duration writeIdleTimeout;
|
||||||
int maxRequestSize;
|
int maxRequestSize;
|
||||||
|
int chunkSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@@ -133,6 +143,7 @@ public class Configuration {
|
|||||||
int incomingConnectionsBacklogSize,
|
int incomingConnectionsBacklogSize,
|
||||||
String serverPath,
|
String serverPath,
|
||||||
EventExecutor eventExecutor,
|
EventExecutor eventExecutor,
|
||||||
|
RateLimiter rateLimiter,
|
||||||
Connection connection,
|
Connection connection,
|
||||||
Map<String, User> users,
|
Map<String, User> users,
|
||||||
Map<String, Group> groups,
|
Map<String, Group> groups,
|
||||||
@@ -146,6 +157,7 @@ public class Configuration {
|
|||||||
incomingConnectionsBacklogSize,
|
incomingConnectionsBacklogSize,
|
||||||
serverPath != null && !serverPath.isEmpty() && !serverPath.equals("/") ? serverPath : null,
|
serverPath != null && !serverPath.isEmpty() && !serverPath.equals("/") ? serverPath : null,
|
||||||
eventExecutor,
|
eventExecutor,
|
||||||
|
rateLimiter,
|
||||||
connection,
|
connection,
|
||||||
users,
|
users,
|
||||||
groups,
|
groups,
|
||||||
|
@@ -14,17 +14,26 @@ public sealed interface CacheMessage {
|
|||||||
private final String key;
|
private final String key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
abstract sealed class CacheGetResponse implements CacheMessage {
|
abstract sealed class CacheGetResponse implements CacheMessage {
|
||||||
|
private final String key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
|
||||||
final class CacheValueFoundResponse extends CacheGetResponse {
|
final class CacheValueFoundResponse extends CacheGetResponse {
|
||||||
private final String key;
|
|
||||||
private final CacheValueMetadata metadata;
|
private final CacheValueMetadata metadata;
|
||||||
|
|
||||||
|
public CacheValueFoundResponse(String key, CacheValueMetadata metadata) {
|
||||||
|
super(key);
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class CacheValueNotFoundResponse extends CacheGetResponse {
|
final class CacheValueNotFoundResponse extends CacheGetResponse {
|
||||||
|
public CacheValueNotFoundResponse(String key) {
|
||||||
|
super(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@@ -9,14 +9,10 @@ plugins {
|
|||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
import net.woggioni.gradle.envelope.EnvelopePlugin
|
|
||||||
import net.woggioni.gradle.envelope.EnvelopeJarTask
|
import net.woggioni.gradle.envelope.EnvelopeJarTask
|
||||||
import net.woggioni.gradle.graalvm.NativeImageConfigurationTask
|
import net.woggioni.gradle.envelope.EnvelopePlugin
|
||||||
import net.woggioni.gradle.graalvm.NativeImageTask
|
import net.woggioni.gradle.graalvm.*
|
||||||
import net.woggioni.gradle.graalvm.NativeImagePlugin
|
|
||||||
import net.woggioni.gradle.graalvm.UpxTask
|
|
||||||
import net.woggioni.gradle.graalvm.JlinkPlugin
|
|
||||||
import net.woggioni.gradle.graalvm.JlinkTask
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
configureNativeImage {
|
configureNativeImage {
|
||||||
@@ -29,6 +25,7 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
|
|
||||||
release {
|
release {
|
||||||
transitive = false
|
transitive = false
|
||||||
canBeConsumed = true
|
canBeConsumed = true
|
||||||
@@ -91,6 +88,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,
|
||||||
@@ -101,6 +102,7 @@ tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfi
|
|||||||
systemProperty('io.netty.leakDetectionLevel', 'DISABLED')
|
systemProperty('io.netty.leakDetectionLevel', 'DISABLED')
|
||||||
modularity.inferModulePath = false
|
modularity.inferModulePath = false
|
||||||
enabled = true
|
enabled = true
|
||||||
|
systemProperty('gradle.tmp.dir', temporaryDir.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeImage {
|
nativeImage {
|
||||||
@@ -115,16 +117,36 @@ nativeImage {
|
|||||||
linkAtBuildTime = false
|
linkAtBuildTime = false
|
||||||
classpath = project.files(jarTaskProvider, configurations.nativeImage)
|
classpath = project.files(jarTaskProvider, configurations.nativeImage)
|
||||||
compressExecutable = true
|
compressExecutable = true
|
||||||
compressionLevel = 10
|
compressionLevel = 6
|
||||||
useLZMA = false
|
useLZMA = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Provider<UpxTask> upxTaskProvider = tasks.named(NativeImagePlugin.UPX_TASK_NAME, UpxTask) {
|
Provider<UpxTask> upxTaskProvider = tasks.named(NativeImagePlugin.UPX_TASK_NAME, UpxTask) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) {
|
Provider<JlinkTask> jlinkTaskProvider = tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
vendor = JvmVendorSpec.GRAAL_VM
|
||||||
|
}
|
||||||
|
|
||||||
mainClass = mainClassName
|
mainClass = mainClassName
|
||||||
mainModule = 'net.woggioni.rbcs.cli'
|
mainModule = 'net.woggioni.rbcs.cli'
|
||||||
|
classpath = project.files(
|
||||||
|
configurations.configureNativeImageRuntimeClasspath,
|
||||||
|
sourceSets.configureNativeImage.output
|
||||||
|
)
|
||||||
|
additionalModules = [
|
||||||
|
'net.woggioni.rbcs.server.memcache',
|
||||||
|
'ch.qos.logback.classic',
|
||||||
|
'jdk.crypto.ec'
|
||||||
|
]
|
||||||
|
compressionLevel = 2
|
||||||
|
stripDebug = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider<Tar> jlinkDistTarTaskProvider = tasks.named(JlinkPlugin.JLINK_DIST_TAR_TASK_NAME, Tar) {
|
||||||
|
exclude 'lib/libjvmcicompiler.so'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, ProcessResources) {
|
tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, ProcessResources) {
|
||||||
@@ -138,6 +160,7 @@ tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, ProcessResources) {
|
|||||||
artifacts {
|
artifacts {
|
||||||
release(envelopeJarTaskProvider)
|
release(envelopeJarTaskProvider)
|
||||||
release(upxTaskProvider)
|
release(upxTaskProvider)
|
||||||
|
release(jlinkDistTarTaskProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
Args=-O3 -march=skylake --gc=serial --install-exit-handlers --initialize-at-run-time=io.netty --enable-url-protocols=jpms --initialize-at-build-time=net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory,net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory$JpmsHandler
|
Args=-O3 -march=x86-64-v2 --gc=serial --install-exit-handlers --initialize-at-run-time=io.netty --enable-url-protocols=jpms --initialize-at-build-time=net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory,net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory$JpmsHandler
|
||||||
#-H:TraceClassInitialization=io.netty.handler.ssl.BouncyCastleAlpnSslUtils
|
#-H:TraceClassInitialization=io.netty.handler.ssl.BouncyCastleAlpnSslUtils
|
@@ -46,6 +46,9 @@
|
|||||||
{
|
{
|
||||||
"name":"com.github.luben.zstd.Zstd"
|
"name":"com.github.luben.zstd.Zstd"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"com.jcraft.jzlib.JZlib"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"com.sun.crypto.provider.AESCipher$General",
|
"name":"com.sun.crypto.provider.AESCipher$General",
|
||||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
@@ -120,6 +123,14 @@
|
|||||||
"name":"io.netty.buffer.AbstractReferenceCountedByteBuf",
|
"name":"io.netty.buffer.AbstractReferenceCountedByteBuf",
|
||||||
"fields":[{"name":"refCnt"}]
|
"fields":[{"name":"refCnt"}]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.buffer.AdaptivePoolingAllocator$Chunk",
|
||||||
|
"fields":[{"name":"refCnt"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.buffer.AdaptivePoolingAllocator$Magazine",
|
||||||
|
"fields":[{"name":"nextInLine"}]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"io.netty.channel.AbstractChannelHandlerContext",
|
"name":"io.netty.channel.AbstractChannelHandlerContext",
|
||||||
"fields":[{"name":"handlerState"}]
|
"fields":[{"name":"handlerState"}]
|
||||||
@@ -284,20 +295,13 @@
|
|||||||
"fields":[{"name":"producerIndex"}]
|
"fields":[{"name":"producerIndex"}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueConsumerIndexField",
|
"name":"io.netty.util.internal.shaded.org.jctools.queues.MpmcArrayQueueConsumerIndexField",
|
||||||
"fields":[{"name":"consumerIndex"}]
|
"fields":[{"name":"consumerIndex"}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueProducerIndexField",
|
"name":"io.netty.util.internal.shaded.org.jctools.queues.MpmcArrayQueueProducerIndexField",
|
||||||
"fields":[{"name":"producerIndex"}]
|
"fields":[{"name":"producerIndex"}]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueProducerLimitField",
|
|
||||||
"fields":[{"name":"producerLimit"}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name":"java.io.FilePermission"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"java.lang.Object",
|
"name":"java.lang.Object",
|
||||||
"allDeclaredFields":true,
|
"allDeclaredFields":true,
|
||||||
@@ -307,26 +311,14 @@
|
|||||||
"name":"java.lang.ProcessHandle",
|
"name":"java.lang.ProcessHandle",
|
||||||
"methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }]
|
"methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"java.lang.RuntimePermission"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"java.lang.System",
|
"name":"java.lang.System",
|
||||||
"methods":[{"name":"console","parameterTypes":[] }]
|
"methods":[{"name":"console","parameterTypes":[] }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"java.lang.Thread",
|
"name":"java.lang.Thread",
|
||||||
"fields":[{"name":"threadLocalRandomProbe"}]
|
"fields":[{"name":"threadLocalRandomProbe"}],
|
||||||
},
|
"methods":[{"name":"isVirtual","parameterTypes":[] }]
|
||||||
{
|
|
||||||
"name":"java.net.NetPermission"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name":"java.net.SocketPermission"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name":"java.net.URLPermission",
|
|
||||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"java.nio.Bits",
|
"name":"java.nio.Bits",
|
||||||
@@ -358,18 +350,12 @@
|
|||||||
{
|
{
|
||||||
"name":"java.security.AlgorithmParametersSpi"
|
"name":"java.security.AlgorithmParametersSpi"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"java.security.AllPermission"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"java.security.KeyStoreSpi"
|
"name":"java.security.KeyStoreSpi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"java.security.SecureRandomParameters"
|
"name":"java.security.SecureRandomParameters"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"java.security.SecurityPermission"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"java.sql.Connection"
|
"name":"java.sql.Connection"
|
||||||
},
|
},
|
||||||
@@ -444,9 +430,6 @@
|
|||||||
"name":"java.time.ZonedDateTime",
|
"name":"java.time.ZonedDateTime",
|
||||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"java.util.PropertyPermission"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"java.util.concurrent.ForkJoinTask",
|
"name":"java.util.concurrent.ForkJoinTask",
|
||||||
"fields":[{"name":"aux"}, {"name":"status"}]
|
"fields":[{"name":"aux"}, {"name":"status"}]
|
||||||
@@ -467,26 +450,19 @@
|
|||||||
"name":"java.util.concurrent.atomic.Striped64$Cell",
|
"name":"java.util.concurrent.atomic.Striped64$Cell",
|
||||||
"fields":[{"name":"value"}]
|
"fields":[{"name":"value"}]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"java.util.zip.Adler32",
|
|
||||||
"methods":[{"name":"update","parameterTypes":["java.nio.ByteBuffer"] }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name":"java.util.zip.CRC32",
|
|
||||||
"methods":[{"name":"update","parameterTypes":["java.nio.ByteBuffer"] }]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"javax.security.auth.x500.X500Principal",
|
"name":"javax.security.auth.x500.X500Principal",
|
||||||
"fields":[{"name":"thisX500Name"}],
|
"fields":[{"name":"thisX500Name"}],
|
||||||
"methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]
|
"methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"javax.smartcardio.CardPermission"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"jdk.internal.misc.Unsafe",
|
"name":"jdk.internal.misc.Unsafe",
|
||||||
"methods":[{"name":"getUnsafe","parameterTypes":[] }]
|
"methods":[{"name":"getUnsafe","parameterTypes":[] }]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"net.woggioni.rbcs.api.CacheHandler",
|
||||||
|
"methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"net.woggioni.rbcs.cli.RemoteBuildCacheServerCli",
|
"name":"net.woggioni.rbcs.cli.RemoteBuildCacheServerCli",
|
||||||
"allDeclaredFields":true,
|
"allDeclaredFields":true,
|
||||||
@@ -552,11 +528,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"net.woggioni.rbcs.client.RemoteBuildCacheClient$sendRequest$1$operationComplete$responseHandler$1",
|
"name":"net.woggioni.rbcs.client.RemoteBuildCacheClient$sendRequest$1$operationComplete$responseHandler$1",
|
||||||
"methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }]
|
"methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }]
|
||||||
},
|
|
||||||
{
|
|
||||||
"name":"net.woggioni.rbcs.client.RemoteBuildCacheClient$sendRequest$1$operationComplete$timeoutHandler$1",
|
|
||||||
"methods":[{"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"net.woggioni.rbcs.server.RemoteBuildCacheServer$HttpChunkContentCompressor",
|
"name":"net.woggioni.rbcs.server.RemoteBuildCacheServer$HttpChunkContentCompressor",
|
||||||
@@ -588,14 +560,14 @@
|
|||||||
"name":"net.woggioni.rbcs.server.exception.ExceptionHandler",
|
"name":"net.woggioni.rbcs.server.exception.ExceptionHandler",
|
||||||
"methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }]
|
"methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"net.woggioni.rbcs.server.handler.CacheContentHandler",
|
|
||||||
"methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"net.woggioni.rbcs.server.handler.MaxRequestSizeHandler",
|
"name":"net.woggioni.rbcs.server.handler.MaxRequestSizeHandler",
|
||||||
"methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }]
|
"methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"net.woggioni.rbcs.server.handler.ReadTriggerDuplexHandler",
|
||||||
|
"methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"net.woggioni.rbcs.server.handler.ServerHandler",
|
"name":"net.woggioni.rbcs.server.handler.ServerHandler",
|
||||||
"methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }]
|
"methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }]
|
||||||
@@ -619,7 +591,7 @@
|
|||||||
{
|
{
|
||||||
"name":"sun.misc.Unsafe",
|
"name":"sun.misc.Unsafe",
|
||||||
"fields":[{"name":"theUnsafe"}],
|
"fields":[{"name":"theUnsafe"}],
|
||||||
"methods":[{"name":"copyMemory","parameterTypes":["java.lang.Object","long","java.lang.Object","long","long"] }, {"name":"getAndAddLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"getAndSetObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }, {"name":"invokeCleaner","parameterTypes":["java.nio.ByteBuffer"] }, {"name":"storeFence","parameterTypes":[] }]
|
"methods":[{"name":"copyMemory","parameterTypes":["java.lang.Object","long","java.lang.Object","long","long"] }, {"name":"getAndAddLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"getAndSetObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }, {"name":"invokeCleaner","parameterTypes":["java.nio.ByteBuffer"] }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"sun.nio.ch.SelectorImpl",
|
"name":"sun.nio.ch.SelectorImpl",
|
||||||
|
@@ -33,13 +33,9 @@
|
|||||||
}, {
|
}, {
|
||||||
"pattern":"\\Qnet/woggioni/rbcs/client/schema/rbcs-client.xsd\\E"
|
"pattern":"\\Qnet/woggioni/rbcs/client/schema/rbcs-client.xsd\\E"
|
||||||
}, {
|
}, {
|
||||||
"pattern":"\\Qnet/woggioni/rbcs/server/rbcs-default.xml\\E"
|
"pattern":"\\Qnet/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd\\E"
|
||||||
}, {
|
}, {
|
||||||
"pattern":"\\Qnet/woggioni/rbcs/server/schema/rbcs-server.xsd\\E"
|
"pattern":"\\Qnet/woggioni/rbcs/server/schema/rbcs-server.xsd\\E"
|
||||||
}, {
|
|
||||||
"pattern":"\\Q/net/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd\\E"
|
|
||||||
}, {
|
|
||||||
"pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E"
|
|
||||||
}]},
|
}]},
|
||||||
"bundles":[{
|
"bundles":[{
|
||||||
"name":"com.sun.org.apache.xerces.internal.impl.xpath.regex.message",
|
"name":"com.sun.org.apache.xerces.internal.impl.xpath.regex.message",
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
package net.woggioni.rbcs.cli.graal
|
package net.woggioni.rbcs.cli.graal
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.net.URI
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.zip.Deflater
|
||||||
import net.woggioni.jwo.NullOutputStream
|
import net.woggioni.jwo.NullOutputStream
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.api.Configuration.User
|
import net.woggioni.rbcs.api.Configuration.User
|
||||||
@@ -9,6 +16,8 @@ import net.woggioni.rbcs.cli.impl.commands.BenchmarkCommand
|
|||||||
import net.woggioni.rbcs.cli.impl.commands.GetCommand
|
import net.woggioni.rbcs.cli.impl.commands.GetCommand
|
||||||
import net.woggioni.rbcs.cli.impl.commands.HealthCheckCommand
|
import net.woggioni.rbcs.cli.impl.commands.HealthCheckCommand
|
||||||
import net.woggioni.rbcs.cli.impl.commands.PutCommand
|
import net.woggioni.rbcs.cli.impl.commands.PutCommand
|
||||||
|
import net.woggioni.rbcs.client.Configuration as ClientConfiguration
|
||||||
|
import net.woggioni.rbcs.client.impl.Parser as ClientConfigurationParser
|
||||||
import net.woggioni.rbcs.common.HostAndPort
|
import net.woggioni.rbcs.common.HostAndPort
|
||||||
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
||||||
import net.woggioni.rbcs.common.RBCS
|
import net.woggioni.rbcs.common.RBCS
|
||||||
@@ -18,21 +27,12 @@ import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration
|
|||||||
import net.woggioni.rbcs.server.cache.InMemoryCacheConfiguration
|
import net.woggioni.rbcs.server.cache.InMemoryCacheConfiguration
|
||||||
import net.woggioni.rbcs.server.configuration.Parser
|
import net.woggioni.rbcs.server.configuration.Parser
|
||||||
import net.woggioni.rbcs.server.memcache.MemcacheCacheConfiguration
|
import net.woggioni.rbcs.server.memcache.MemcacheCacheConfiguration
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.net.URI
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import java.util.concurrent.ExecutionException
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
import net.woggioni.rbcs.client.Configuration as ClientConfiguration
|
|
||||||
import net.woggioni.rbcs.client.impl.Parser as ClientConfigurationParser
|
|
||||||
|
|
||||||
object GraalNativeImageConfiguration {
|
object GraalNativeImageConfiguration {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(vararg args : String) {
|
fun main(vararg args : String) {
|
||||||
|
|
||||||
val serverURL = URI.create("file:conf/rbcs-client.xml").toURL()
|
val serverURL = URI.create("file:conf/rbcs-server.xml").toURL()
|
||||||
val serverDoc = serverURL.openStream().use {
|
val serverDoc = serverURL.openStream().use {
|
||||||
Xml.parseXml(serverURL, it)
|
Xml.parseXml(serverURL, it)
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,6 @@ object GraalNativeImageConfiguration {
|
|||||||
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
||||||
compressionEnabled = false,
|
compressionEnabled = false,
|
||||||
maxSize = 0x1000000,
|
maxSize = 0x1000000,
|
||||||
chunkSize = 0x1000
|
|
||||||
),
|
),
|
||||||
FileSystemCacheConfiguration(
|
FileSystemCacheConfiguration(
|
||||||
Path.of(System.getProperty("java.io.tmpdir")).resolve("rbcs"),
|
Path.of(System.getProperty("java.io.tmpdir")).resolve("rbcs"),
|
||||||
@@ -79,7 +78,6 @@ object GraalNativeImageConfiguration {
|
|||||||
digestAlgorithm = "MD5",
|
digestAlgorithm = "MD5",
|
||||||
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
||||||
compressionEnabled = false,
|
compressionEnabled = false,
|
||||||
chunkSize = 0x1000
|
|
||||||
),
|
),
|
||||||
MemcacheCacheConfiguration(
|
MemcacheCacheConfiguration(
|
||||||
listOf(MemcacheCacheConfiguration.Server(
|
listOf(MemcacheCacheConfiguration.Server(
|
||||||
@@ -88,10 +86,10 @@ object GraalNativeImageConfiguration {
|
|||||||
4)
|
4)
|
||||||
),
|
),
|
||||||
Duration.ofSeconds(60),
|
Duration.ofSeconds(60),
|
||||||
|
"someCustomPrefix",
|
||||||
"MD5",
|
"MD5",
|
||||||
null,
|
null,
|
||||||
1,
|
1,
|
||||||
0x1000
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,11 +100,15 @@ object GraalNativeImageConfiguration {
|
|||||||
100,
|
100,
|
||||||
null,
|
null,
|
||||||
Configuration.EventExecutor(true),
|
Configuration.EventExecutor(true),
|
||||||
|
Configuration.RateLimiter(
|
||||||
|
false, 0x100000, 10
|
||||||
|
),
|
||||||
Configuration.Connection(
|
Configuration.Connection(
|
||||||
Duration.ofSeconds(10),
|
Duration.ofSeconds(10),
|
||||||
Duration.ofSeconds(15),
|
Duration.ofSeconds(15),
|
||||||
Duration.ofSeconds(15),
|
Duration.ofSeconds(15),
|
||||||
0x10000,
|
0x10000,
|
||||||
|
0x1000
|
||||||
),
|
),
|
||||||
users.asSequence().map { it.name to it }.toMap(),
|
users.asSequence().map { it.name to it }.toMap(),
|
||||||
sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(),
|
sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(),
|
||||||
@@ -115,27 +117,16 @@ object GraalNativeImageConfiguration {
|
|||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
|
||||||
MemcacheCacheConfiguration(
|
|
||||||
listOf(
|
|
||||||
MemcacheCacheConfiguration.Server(
|
|
||||||
HostAndPort("127.0.0.1", 11211),
|
|
||||||
1000,
|
|
||||||
4
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Duration.ofSeconds(60),
|
|
||||||
"MD5",
|
|
||||||
null,
|
|
||||||
1,
|
|
||||||
0x1000
|
|
||||||
)
|
|
||||||
|
|
||||||
val serverHandle = RemoteBuildCacheServer(serverConfiguration).run()
|
val serverHandle = RemoteBuildCacheServer(serverConfiguration).run()
|
||||||
|
|
||||||
|
|
||||||
val clientProfile = ClientConfiguration.Profile(
|
val clientProfile = ClientConfiguration.Profile(
|
||||||
URI.create("http://127.0.0.1:$serverPort/"),
|
URI.create("http://127.0.0.1:$serverPort/"),
|
||||||
null,
|
ClientConfiguration.Connection(
|
||||||
|
Duration.ofSeconds(5),
|
||||||
|
Duration.ofSeconds(5),
|
||||||
|
Duration.ofSeconds(7),
|
||||||
|
true,
|
||||||
|
),
|
||||||
ClientConfiguration.Authentication.BasicAuthenticationCredentials("user3", PASSWORD),
|
ClientConfiguration.Authentication.BasicAuthenticationCredentials("user3", PASSWORD),
|
||||||
Duration.ofSeconds(3),
|
Duration.ofSeconds(3),
|
||||||
10,
|
10,
|
||||||
@@ -177,6 +168,8 @@ object GraalNativeImageConfiguration {
|
|||||||
} catch (ee : ExecutionException) {
|
} catch (ee : ExecutionException) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RemoteBuildCacheServerCli.main("--help")
|
System.setProperty("net.woggioni.rbcs.conf.dir", System.getProperty("gradle.tmp.dir"))
|
||||||
|
RemoteBuildCacheServerCli.createCommandLine().execute("--version")
|
||||||
|
RemoteBuildCacheServerCli.createCommandLine().execute("server", "-t", "PT10S")
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -26,8 +26,8 @@ class RemoteBuildCacheServerCli : RbcsCommand() {
|
|||||||
private fun setPropertyIfNotPresent(key: String, value: String) {
|
private fun setPropertyIfNotPresent(key: String, value: String) {
|
||||||
System.getProperty(key) ?: System.setProperty(key, value)
|
System.getProperty(key) ?: System.setProperty(key, value)
|
||||||
}
|
}
|
||||||
@JvmStatic
|
|
||||||
fun main(vararg args: String) {
|
fun createCommandLine() : CommandLine {
|
||||||
setPropertyIfNotPresent("logback.configurationFile", "net/woggioni/rbcs/cli/logback.xml")
|
setPropertyIfNotPresent("logback.configurationFile", "net/woggioni/rbcs/cli/logback.xml")
|
||||||
setPropertyIfNotPresent("io.netty.leakDetectionLevel", "DISABLED")
|
setPropertyIfNotPresent("io.netty.leakDetectionLevel", "DISABLED")
|
||||||
val currentClassLoader = RemoteBuildCacheServerCli::class.java.classLoader
|
val currentClassLoader = RemoteBuildCacheServerCli::class.java.classLoader
|
||||||
@@ -56,7 +56,12 @@ class RemoteBuildCacheServerCli : RbcsCommand() {
|
|||||||
addSubcommand(GetCommand())
|
addSubcommand(GetCommand())
|
||||||
addSubcommand(HealthCheckCommand())
|
addSubcommand(HealthCheckCommand())
|
||||||
})
|
})
|
||||||
System.exit(commandLine.execute(*args))
|
return commandLine
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun main(vararg args: String) {
|
||||||
|
System.exit(createCommandLine().execute(*args))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package net.woggioni.rbcs.cli.impl
|
package net.woggioni.rbcs.cli.impl
|
||||||
|
|
||||||
import picocli.CommandLine
|
|
||||||
import java.util.jar.Attributes
|
import java.util.jar.Attributes
|
||||||
import java.util.jar.JarFile
|
import java.util.jar.JarFile
|
||||||
import java.util.jar.Manifest
|
import java.util.jar.Manifest
|
||||||
|
import picocli.CommandLine
|
||||||
|
|
||||||
|
|
||||||
abstract class AbstractVersionProvider : CommandLine.IVersionProvider {
|
abstract class AbstractVersionProvider : CommandLine.IVersionProvider {
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package net.woggioni.rbcs.cli.impl
|
package net.woggioni.rbcs.cli.impl
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
import net.woggioni.jwo.Application
|
import net.woggioni.jwo.Application
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
abstract class RbcsCommand : Runnable {
|
abstract class RbcsCommand : Runnable {
|
||||||
@@ -12,7 +12,7 @@ abstract class RbcsCommand : Runnable {
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
protected fun findConfigurationFile(app: Application, fileName : String): Path {
|
protected fun findConfigurationFile(app: Application, fileName : String): Path {
|
||||||
val confDir = app.computeConfigurationDirectory()
|
val confDir = app.computeConfigurationDirectory(false)
|
||||||
val configurationFile = confDir.resolve(fileName)
|
val configurationFile = confDir.resolve(fileName)
|
||||||
return configurationFile
|
return configurationFile
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,13 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.commands
|
package net.woggioni.rbcs.cli.impl.commands
|
||||||
|
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
import java.util.concurrent.Semaphore
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
import kotlin.random.Random
|
||||||
import net.woggioni.jwo.JWO
|
import net.woggioni.jwo.JWO
|
||||||
import net.woggioni.jwo.LongMath
|
import net.woggioni.jwo.LongMath
|
||||||
import net.woggioni.rbcs.api.CacheValueMetadata
|
import net.woggioni.rbcs.api.CacheValueMetadata
|
||||||
@@ -12,14 +20,6 @@ import net.woggioni.rbcs.common.debug
|
|||||||
import net.woggioni.rbcs.common.error
|
import net.woggioni.rbcs.common.error
|
||||||
import net.woggioni.rbcs.common.info
|
import net.woggioni.rbcs.common.info
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
|
||||||
import java.util.concurrent.Semaphore
|
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "benchmark",
|
name = "benchmark",
|
||||||
@@ -101,6 +101,7 @@ class BenchmarkCommand : RbcsCommand() {
|
|||||||
"Starting retrieval"
|
"Starting retrieval"
|
||||||
}
|
}
|
||||||
if (entries.isNotEmpty()) {
|
if (entries.isNotEmpty()) {
|
||||||
|
val errorCounter = AtomicLong(0)
|
||||||
val completionCounter = AtomicLong(0)
|
val completionCounter = AtomicLong(0)
|
||||||
val semaphore = Semaphore(profile.maxConnections * 5)
|
val semaphore = Semaphore(profile.maxConnections * 5)
|
||||||
val start = Instant.now()
|
val start = Instant.now()
|
||||||
@@ -109,14 +110,20 @@ class BenchmarkCommand : RbcsCommand() {
|
|||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
val entry = it.next()
|
val entry = it.next()
|
||||||
semaphore.acquire()
|
semaphore.acquire()
|
||||||
val future = client.get(entry.first).thenApply {
|
val future = client.get(entry.first).handle { response, ex ->
|
||||||
if (it == null) {
|
if(ex != null) {
|
||||||
|
errorCounter.incrementAndGet()
|
||||||
|
log.error(ex.message, ex)
|
||||||
|
} else if (response == null) {
|
||||||
|
errorCounter.incrementAndGet()
|
||||||
log.error {
|
log.error {
|
||||||
"Missing entry for key '${entry.first}'"
|
"Missing entry for key '${entry.first}'"
|
||||||
}
|
}
|
||||||
} else if (!entry.second.contentEquals(it)) {
|
} else if (!entry.second.contentEquals(response)) {
|
||||||
|
errorCounter.incrementAndGet()
|
||||||
log.error {
|
log.error {
|
||||||
"Retrieved a value different from what was inserted for key '${entry.first}'"
|
"Retrieved a value different from what was inserted for key '${entry.first}': " +
|
||||||
|
"expected '${JWO.bytesToHex(entry.second)}', got '${JWO.bytesToHex(response)}' instead"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,6 +141,12 @@ class BenchmarkCommand : RbcsCommand() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val end = Instant.now()
|
val end = Instant.now()
|
||||||
|
val errors = errorCounter.get()
|
||||||
|
val successfulRetrievals = entries.size - errors
|
||||||
|
val successRate = successfulRetrievals.toDouble() / entries.size
|
||||||
|
log.info {
|
||||||
|
"Successfully retrieved ${entries.size - errors}/${entries.size} (${String.format("%.1f", successRate * 100)}%)"
|
||||||
|
}
|
||||||
log.info {
|
log.info {
|
||||||
val elapsed = Duration.between(start, end).toMillis()
|
val elapsed = Duration.between(start, end).toMillis()
|
||||||
val opsPerSecond = String.format("%.2f", entries.size.toDouble() / elapsed * 1000)
|
val opsPerSecond = String.format("%.2f", entries.size.toDouble() / elapsed * 1000)
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.commands
|
package net.woggioni.rbcs.cli.impl.commands
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
import net.woggioni.jwo.Application
|
import net.woggioni.jwo.Application
|
||||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||||
import net.woggioni.rbcs.client.Configuration
|
import net.woggioni.rbcs.client.Configuration
|
||||||
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 picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "client",
|
name = "client",
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.commands
|
package net.woggioni.rbcs.cli.impl.commands
|
||||||
|
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||||
import net.woggioni.rbcs.client.Configuration
|
import net.woggioni.rbcs.client.Configuration
|
||||||
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
||||||
import net.woggioni.rbcs.common.createLogger
|
import net.woggioni.rbcs.common.createLogger
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.io.OutputStream
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "get",
|
name = "get",
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.commands
|
package net.woggioni.rbcs.cli.impl.commands
|
||||||
|
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import kotlin.random.Random
|
||||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||||
import net.woggioni.rbcs.client.Configuration
|
import net.woggioni.rbcs.client.Configuration
|
||||||
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
||||||
import net.woggioni.rbcs.common.createLogger
|
import net.woggioni.rbcs.common.createLogger
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.security.SecureRandom
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "health",
|
name = "health",
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.commands
|
package net.woggioni.rbcs.cli.impl.commands
|
||||||
|
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
|
import java.io.PrintWriter
|
||||||
import net.woggioni.jwo.UncloseableOutputStream
|
import net.woggioni.jwo.UncloseableOutputStream
|
||||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||||
import net.woggioni.rbcs.cli.impl.converters.OutputStreamConverter
|
import net.woggioni.rbcs.cli.impl.converters.OutputStreamConverter
|
||||||
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.io.OutputStream
|
|
||||||
import java.io.OutputStreamWriter
|
|
||||||
import java.io.PrintWriter
|
|
||||||
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.commands
|
package net.woggioni.rbcs.cli.impl.commands
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.UUID
|
||||||
import net.woggioni.jwo.Hash
|
import net.woggioni.jwo.Hash
|
||||||
import net.woggioni.jwo.JWO
|
import net.woggioni.jwo.JWO
|
||||||
import net.woggioni.jwo.NullOutputStream
|
import net.woggioni.jwo.NullOutputStream
|
||||||
@@ -9,10 +13,6 @@ import net.woggioni.rbcs.client.Configuration
|
|||||||
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
||||||
import net.woggioni.rbcs.common.createLogger
|
import net.woggioni.rbcs.common.createLogger
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.io.InputStream
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "put",
|
name = "put",
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.commands
|
package net.woggioni.rbcs.cli.impl.commands
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import net.woggioni.jwo.Application
|
import net.woggioni.jwo.Application
|
||||||
import net.woggioni.jwo.JWO
|
import net.woggioni.jwo.JWO
|
||||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||||
@@ -10,11 +15,6 @@ import net.woggioni.rbcs.common.info
|
|||||||
import net.woggioni.rbcs.server.RemoteBuildCacheServer
|
import net.woggioni.rbcs.server.RemoteBuildCacheServer
|
||||||
import net.woggioni.rbcs.server.RemoteBuildCacheServer.Companion.DEFAULT_CONFIGURATION_URL
|
import net.woggioni.rbcs.server.RemoteBuildCacheServer.Companion.DEFAULT_CONFIGURATION_URL
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.time.Duration
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "server",
|
name = "server",
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.converters
|
package net.woggioni.rbcs.cli.impl.converters
|
||||||
|
|
||||||
import picocli.CommandLine
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import picocli.CommandLine
|
||||||
|
|
||||||
|
|
||||||
class DurationConverter : CommandLine.ITypeConverter<Duration> {
|
class DurationConverter : CommandLine.ITypeConverter<Duration> {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.converters
|
package net.woggioni.rbcs.cli.impl.converters
|
||||||
|
|
||||||
import picocli.CommandLine
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
import picocli.CommandLine
|
||||||
|
|
||||||
|
|
||||||
class InputStreamConverter : CommandLine.ITypeConverter<InputStream> {
|
class InputStreamConverter : CommandLine.ITypeConverter<InputStream> {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package net.woggioni.rbcs.cli.impl.converters
|
package net.woggioni.rbcs.cli.impl.converters
|
||||||
|
|
||||||
import picocli.CommandLine
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
import picocli.CommandLine
|
||||||
|
|
||||||
|
|
||||||
class OutputStreamConverter : CommandLine.ITypeConverter<OutputStream> {
|
class OutputStreamConverter : CommandLine.ITypeConverter<OutputStream> {
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
package net.woggioni.rbcs.client
|
package net.woggioni.rbcs.client
|
||||||
|
|
||||||
import net.woggioni.rbcs.client.impl.Parser
|
|
||||||
import net.woggioni.rbcs.common.Xml
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import net.woggioni.rbcs.client.impl.Parser
|
||||||
|
import net.woggioni.rbcs.common.Xml
|
||||||
|
|
||||||
data class Configuration(
|
data class Configuration(
|
||||||
val profiles: Map<String, Profile>
|
val profiles: Map<String, Profile>
|
||||||
@@ -38,11 +38,12 @@ data class Configuration(
|
|||||||
val readIdleTimeout: Duration,
|
val readIdleTimeout: Duration,
|
||||||
val writeIdleTimeout: Duration,
|
val writeIdleTimeout: Duration,
|
||||||
val idleTimeout: Duration,
|
val idleTimeout: Duration,
|
||||||
|
val requestPipelining : Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Profile(
|
data class Profile(
|
||||||
val serverURI: URI,
|
val serverURI: URI,
|
||||||
val connection: Connection?,
|
val connection: Connection,
|
||||||
val authentication: Authentication?,
|
val authentication: Authentication?,
|
||||||
val connectionTimeout: Duration?,
|
val connectionTimeout: Duration?,
|
||||||
val maxConnections: Int,
|
val maxConnections: Int,
|
||||||
|
@@ -4,13 +4,13 @@ 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.IoEventLoopGroup
|
||||||
|
import io.netty.channel.MultiThreadIoEventLoopGroup
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
import io.netty.channel.nio.NioEventLoopGroup
|
import io.netty.channel.nio.NioIoHandler
|
||||||
import io.netty.channel.pool.AbstractChannelPoolHandler
|
import io.netty.channel.pool.AbstractChannelPoolHandler
|
||||||
import io.netty.channel.pool.ChannelPool
|
import io.netty.channel.pool.ChannelPool
|
||||||
import io.netty.channel.pool.FixedChannelPool
|
import io.netty.channel.pool.FixedChannelPool
|
||||||
@@ -34,12 +34,8 @@ import io.netty.handler.timeout.IdleState
|
|||||||
import io.netty.handler.timeout.IdleStateEvent
|
import io.netty.handler.timeout.IdleStateEvent
|
||||||
import io.netty.handler.timeout.IdleStateHandler
|
import io.netty.handler.timeout.IdleStateHandler
|
||||||
import io.netty.util.concurrent.Future
|
import io.netty.util.concurrent.Future
|
||||||
|
import io.netty.util.concurrent.Future as NettyFuture
|
||||||
import io.netty.util.concurrent.GenericFutureListener
|
import io.netty.util.concurrent.GenericFutureListener
|
||||||
import net.woggioni.rbcs.api.CacheValueMetadata
|
|
||||||
import net.woggioni.rbcs.common.RBCS.loadKeystore
|
|
||||||
import net.woggioni.rbcs.common.createLogger
|
|
||||||
import net.woggioni.rbcs.common.debug
|
|
||||||
import net.woggioni.rbcs.common.trace
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@@ -52,19 +48,23 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||||||
import javax.net.ssl.TrustManagerFactory
|
import javax.net.ssl.TrustManagerFactory
|
||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
import io.netty.util.concurrent.Future as NettyFuture
|
import net.woggioni.rbcs.api.CacheValueMetadata
|
||||||
|
import net.woggioni.rbcs.common.RBCS.loadKeystore
|
||||||
|
import net.woggioni.rbcs.common.createLogger
|
||||||
|
import net.woggioni.rbcs.common.debug
|
||||||
|
import net.woggioni.rbcs.common.trace
|
||||||
|
|
||||||
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>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val group: NioEventLoopGroup
|
private val group: IoEventLoopGroup
|
||||||
private val sslContext: SslContext
|
private val sslContext: SslContext
|
||||||
private val pool: ChannelPool
|
private val pool: ChannelPool
|
||||||
|
|
||||||
init {
|
init {
|
||||||
group = NioEventLoopGroup()
|
group = MultiThreadIoEventLoopGroup(NioIoHandler.newFactory())
|
||||||
sslContext = SslContextBuilder.forClient().also { builder ->
|
sslContext = SslContextBuilder.forClient().also { builder ->
|
||||||
(profile.authentication as? Configuration.Authentication.TlsClientAuthenticationCredentials)?.let { tlsClientAuthenticationCredentials ->
|
(profile.authentication as? Configuration.Authentication.TlsClientAuthenticationCredentials)?.let { tlsClientAuthenticationCredentials ->
|
||||||
builder.apply {
|
builder.apply {
|
||||||
@@ -73,7 +73,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?) {
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
}
|
}
|
||||||
val pipeline: ChannelPipeline = ch.pipeline()
|
val pipeline: ChannelPipeline = ch.pipeline()
|
||||||
|
|
||||||
profile.connection?.also { conn ->
|
profile.connection.also { conn ->
|
||||||
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()
|
||||||
@@ -176,7 +176,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))
|
||||||
@@ -254,19 +254,25 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
fun get(key: String): CompletableFuture<ByteArray?> {
|
fun get(key: String): CompletableFuture<ByteArray?> {
|
||||||
return executeWithRetry {
|
return executeWithRetry {
|
||||||
sendRequest(profile.serverURI.resolve(key), HttpMethod.GET, null)
|
sendRequest(profile.serverURI.resolve(key), HttpMethod.GET, null)
|
||||||
}.thenApply {
|
}.thenApply { response ->
|
||||||
val status = it.status()
|
val status = response.status()
|
||||||
if (it.status() == HttpResponseStatus.NOT_FOUND) {
|
if (response.status() == HttpResponseStatus.NOT_FOUND) {
|
||||||
|
response.release()
|
||||||
null
|
null
|
||||||
} else if (it.status() != HttpResponseStatus.OK) {
|
} else if (response.status() != HttpResponseStatus.OK) {
|
||||||
|
response.release()
|
||||||
throw HttpException(status)
|
throw HttpException(status)
|
||||||
} else {
|
} else {
|
||||||
it.content()
|
response.content().also {
|
||||||
|
it.retain()
|
||||||
|
response.release()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.thenApply { maybeByteBuf ->
|
}.thenApply { maybeByteBuf ->
|
||||||
maybeByteBuf?.let {
|
maybeByteBuf?.let { buf ->
|
||||||
val result = ByteArray(it.readableBytes())
|
val result = ByteArray(buf.readableBytes())
|
||||||
it.getBytes(0, result)
|
buf.getBytes(0, result)
|
||||||
|
buf.release()
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,50 +301,33 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
): CompletableFuture<FullHttpResponse> {
|
): CompletableFuture<FullHttpResponse> {
|
||||||
val responseFuture = CompletableFuture<FullHttpResponse>()
|
val responseFuture = CompletableFuture<FullHttpResponse>()
|
||||||
// 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.retainedDuplicate())
|
||||||
responseFuture.complete(response)
|
if (!profile.connection.requestPipelining) {
|
||||||
|
pool.release(channel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
@@ -352,16 +341,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)
|
||||||
|
pool.release(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
if (!profile.connection.requestPipelining) {
|
||||||
|
pool.release(channel)
|
||||||
|
}
|
||||||
|
} 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 +379,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 +405,16 @@ 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)
|
||||||
|
}
|
||||||
|
if (profile.connection.requestPipelining) {
|
||||||
|
pool.release(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
responseFuture.completeExceptionally(channelFuture.cause())
|
responseFuture.completeExceptionally(channelFuture.cause())
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,5 @@
|
|||||||
package net.woggioni.rbcs.client.impl
|
package net.woggioni.rbcs.client.impl
|
||||||
|
|
||||||
import net.woggioni.rbcs.api.exception.ConfigurationException
|
|
||||||
import net.woggioni.rbcs.client.Configuration
|
|
||||||
import net.woggioni.rbcs.common.Xml.Companion.asIterable
|
|
||||||
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
|
||||||
import org.w3c.dom.Document
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@@ -13,6 +8,11 @@ import java.security.PrivateKey
|
|||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
import net.woggioni.rbcs.api.exception.ConfigurationException
|
||||||
|
import net.woggioni.rbcs.client.Configuration
|
||||||
|
import net.woggioni.rbcs.common.Xml.Companion.asIterable
|
||||||
|
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
|
||||||
object Parser {
|
object Parser {
|
||||||
|
|
||||||
@@ -30,7 +30,12 @@ object Parser {
|
|||||||
?: throw ConfigurationException("base-url attribute is required")
|
?: throw ConfigurationException("base-url attribute is required")
|
||||||
var authentication: Configuration.Authentication? = null
|
var authentication: Configuration.Authentication? = null
|
||||||
var retryPolicy: Configuration.RetryPolicy? = null
|
var retryPolicy: Configuration.RetryPolicy? = null
|
||||||
var connection : Configuration.Connection? = null
|
var connection : Configuration.Connection = Configuration.Connection(
|
||||||
|
Duration.ofSeconds(60),
|
||||||
|
Duration.ofSeconds(60),
|
||||||
|
Duration.ofSeconds(30),
|
||||||
|
false
|
||||||
|
)
|
||||||
var trustStore : Configuration.TrustStore? = null
|
var trustStore : Configuration.TrustStore? = null
|
||||||
for (gchild in child.asIterable()) {
|
for (gchild in child.asIterable()) {
|
||||||
when (gchild.localName) {
|
when (gchild.localName) {
|
||||||
@@ -97,10 +102,13 @@ object Parser {
|
|||||||
?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS)
|
?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS)
|
||||||
val writeIdleTimeout = gchild.renderAttribute("write-idle-timeout")
|
val writeIdleTimeout = gchild.renderAttribute("write-idle-timeout")
|
||||||
?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS)
|
?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS)
|
||||||
|
val requestPipelining = gchild.renderAttribute("request-pipelining")
|
||||||
|
?.let(String::toBoolean) ?: false
|
||||||
connection = Configuration.Connection(
|
connection = Configuration.Connection(
|
||||||
readIdleTimeout,
|
readIdleTimeout,
|
||||||
writeIdleTimeout,
|
writeIdleTimeout,
|
||||||
idleTimeout,
|
idleTimeout,
|
||||||
|
requestPipelining
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -123,6 +123,13 @@
|
|||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="request-pipelining" type="xs:boolean" use="optional" default="false">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Enables HTTP/1.1 request pipelining
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="noAuthType">
|
<xs:complexType name="noAuthType">
|
||||||
|
@@ -2,6 +2,9 @@ package net.woggioni.rbcs.client
|
|||||||
|
|
||||||
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
||||||
import io.netty.util.concurrent.EventExecutorGroup
|
import io.netty.util.concurrent.EventExecutorGroup
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.stream.Stream
|
||||||
|
import kotlin.random.Random
|
||||||
import net.woggioni.rbcs.common.contextLogger
|
import net.woggioni.rbcs.common.contextLogger
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.extension.ExtensionContext
|
import org.junit.jupiter.api.extension.ExtensionContext
|
||||||
@@ -9,9 +12,6 @@ import org.junit.jupiter.params.ParameterizedTest
|
|||||||
import org.junit.jupiter.params.provider.Arguments
|
import org.junit.jupiter.params.provider.Arguments
|
||||||
import org.junit.jupiter.params.provider.ArgumentsProvider
|
import org.junit.jupiter.params.provider.ArgumentsProvider
|
||||||
import org.junit.jupiter.params.provider.ArgumentsSource
|
import org.junit.jupiter.params.provider.ArgumentsSource
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.stream.Stream
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
class RetryTest {
|
class RetryTest {
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':rbcs-api')
|
implementation catalog.netty.transport
|
||||||
implementation catalog.slf4j.api
|
implementation catalog.slf4j.api
|
||||||
implementation catalog.jwo
|
implementation catalog.jwo
|
||||||
implementation catalog.netty.buffer
|
implementation catalog.netty.buffer
|
||||||
|
@@ -2,14 +2,14 @@ package net.woggioni.rbcs.common
|
|||||||
|
|
||||||
import io.netty.channel.Channel
|
import io.netty.channel.Channel
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.logging.LogManager
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.slf4j.MDC
|
import org.slf4j.MDC
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
import org.slf4j.spi.LoggingEventBuilder
|
import org.slf4j.spi.LoggingEventBuilder
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.util.logging.LogManager
|
|
||||||
|
|
||||||
inline fun <reified T> T.contextLogger() = LoggerFactory.getLogger(T::class.java)
|
inline fun <reified T> T.contextLogger() = LoggerFactory.getLogger(T::class.java)
|
||||||
inline fun <reified T> createLogger() = LoggerFactory.getLogger(T::class.java)
|
inline fun <reified T> createLogger() = LoggerFactory.getLogger(T::class.java)
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
package net.woggioni.rbcs.common
|
package net.woggioni.rbcs.common
|
||||||
|
|
||||||
import net.woggioni.jwo.JWO
|
|
||||||
import net.woggioni.jwo.Tuple2
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.ServerSocket
|
import java.net.ServerSocket
|
||||||
@@ -21,26 +19,28 @@ import java.security.cert.X509Certificate
|
|||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import javax.net.ssl.TrustManagerFactory
|
import javax.net.ssl.TrustManagerFactory
|
||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
|
import net.woggioni.jwo.JWO
|
||||||
|
import net.woggioni.jwo.Tuple2
|
||||||
|
|
||||||
object RBCS {
|
object RBCS {
|
||||||
fun String.toUrl() : URL = URL.of(URI(this), null)
|
fun String.toUrl(): URL = URL.of(URI(this), null)
|
||||||
|
|
||||||
const val RBCS_NAMESPACE_URI: String = "urn:net.woggioni.rbcs.server"
|
const val RBCS_NAMESPACE_URI: String = "urn:net.woggioni.rbcs.server"
|
||||||
const val RBCS_PREFIX: String = "rbcs"
|
const val RBCS_PREFIX: String = "rbcs"
|
||||||
const val XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance"
|
const val XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
|
||||||
fun ByteArray.toInt(index : Int = 0) : Long {
|
fun ByteArray.toInt(index: Int = 0): Long {
|
||||||
if(index + 4 > size) throw IllegalArgumentException("Not enough bytes to decode a 32 bits integer")
|
if (index + 4 > size) throw IllegalArgumentException("Not enough bytes to decode a 32 bits integer")
|
||||||
var value : Long = 0
|
var value: Long = 0
|
||||||
for (b in index until index + 4) {
|
for (b in index until index + 4) {
|
||||||
value = (value shl 8) + (get(b).toInt() and 0xFF)
|
value = (value shl 8) + (get(b).toInt() and 0xFF)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ByteArray.toLong(index : Int = 0) : Long {
|
fun ByteArray.toLong(index: Int = 0): Long {
|
||||||
if(index + 8 > size) throw IllegalArgumentException("Not enough bytes to decode a 64 bits long integer")
|
if (index + 8 > size) throw IllegalArgumentException("Not enough bytes to decode a 64 bits long integer")
|
||||||
var value : Long = 0
|
var value: Long = 0
|
||||||
for (b in index until index + 8) {
|
for (b in index until index + 8) {
|
||||||
value = (value shl 8) + (get(b).toInt() and 0xFF)
|
value = (value shl 8) + (get(b).toInt() and 0xFF)
|
||||||
}
|
}
|
||||||
@@ -62,11 +62,18 @@ object RBCS {
|
|||||||
return JWO.bytesToHex(digest(data, md))
|
return JWO.bytesToHex(digest(data, md))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processCacheKey(key: String, digestAlgorithm: String?) = digestAlgorithm
|
fun processCacheKey(key: String, keyPrefix: String?, digestAlgorithm: String?) : ByteArray {
|
||||||
?.let(MessageDigest::getInstance)
|
val prefixedKey = if (keyPrefix == null) {
|
||||||
?.let { md ->
|
key
|
||||||
digest(key.toByteArray(), md)
|
} else {
|
||||||
} ?: key.toByteArray(Charsets.UTF_8)
|
key + keyPrefix
|
||||||
|
}.toByteArray(Charsets.UTF_8)
|
||||||
|
return digestAlgorithm
|
||||||
|
?.let(MessageDigest::getInstance)
|
||||||
|
?.let { md ->
|
||||||
|
digest(prefixedKey, md)
|
||||||
|
} ?: prefixedKey
|
||||||
|
}
|
||||||
|
|
||||||
fun Long.toIntOrNull(): Int? {
|
fun Long.toIntOrNull(): Int? {
|
||||||
return if (this >= Int.MIN_VALUE && this <= Int.MAX_VALUE) {
|
return if (this >= Int.MIN_VALUE && this <= Int.MAX_VALUE) {
|
||||||
|
@@ -1,14 +1,5 @@
|
|||||||
package net.woggioni.rbcs.common
|
package net.woggioni.rbcs.common
|
||||||
|
|
||||||
import net.woggioni.jwo.JWO
|
|
||||||
import org.slf4j.event.Level
|
|
||||||
import org.w3c.dom.Document
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
import org.w3c.dom.NodeList
|
|
||||||
import org.xml.sax.SAXNotRecognizedException
|
|
||||||
import org.xml.sax.SAXNotSupportedException
|
|
||||||
import org.xml.sax.SAXParseException
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@@ -25,7 +16,16 @@ import javax.xml.transform.stream.StreamResult
|
|||||||
import javax.xml.transform.stream.StreamSource
|
import javax.xml.transform.stream.StreamSource
|
||||||
import javax.xml.validation.Schema
|
import javax.xml.validation.Schema
|
||||||
import javax.xml.validation.SchemaFactory
|
import javax.xml.validation.SchemaFactory
|
||||||
|
import net.woggioni.jwo.JWO
|
||||||
|
import org.slf4j.event.Level
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import org.w3c.dom.Node
|
||||||
|
import org.w3c.dom.NodeList
|
||||||
import org.xml.sax.ErrorHandler as ErrHandler
|
import org.xml.sax.ErrorHandler as ErrHandler
|
||||||
|
import org.xml.sax.SAXNotRecognizedException
|
||||||
|
import org.xml.sax.SAXNotSupportedException
|
||||||
|
import org.xml.sax.SAXParseException
|
||||||
|
|
||||||
|
|
||||||
class NodeListIterator(private val nodeList: NodeList) : Iterator<Node> {
|
class NodeListIterator(private val nodeList: NodeList) : Iterator<Node> {
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
package net.woggioni.rbcs.common
|
package net.woggioni.rbcs.common
|
||||||
|
|
||||||
|
import java.security.Provider
|
||||||
|
import java.security.Security
|
||||||
|
import java.util.Base64
|
||||||
import net.woggioni.rbcs.common.PasswordSecurity.decodePasswordHash
|
import net.woggioni.rbcs.common.PasswordSecurity.decodePasswordHash
|
||||||
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.params.ParameterizedTest
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
import org.junit.jupiter.params.provider.EnumSource
|
import org.junit.jupiter.params.provider.EnumSource
|
||||||
import java.security.Provider
|
|
||||||
import java.security.Security
|
|
||||||
import java.util.Base64
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordHashingTest {
|
class PasswordHashingTest {
|
||||||
|
@@ -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>
|
||||||
|
@@ -1,29 +1,29 @@
|
|||||||
package net.woggioni.rbcs.server.memcache
|
package net.woggioni.rbcs.server.memcache
|
||||||
|
|
||||||
import io.netty.channel.ChannelFactory
|
import io.netty.channel.ChannelFactory
|
||||||
import io.netty.channel.ChannelHandler
|
|
||||||
import io.netty.channel.EventLoopGroup
|
import io.netty.channel.EventLoopGroup
|
||||||
import io.netty.channel.pool.FixedChannelPool
|
import io.netty.channel.pool.FixedChannelPool
|
||||||
import io.netty.channel.socket.DatagramChannel
|
import io.netty.channel.socket.DatagramChannel
|
||||||
import io.netty.channel.socket.SocketChannel
|
import io.netty.channel.socket.SocketChannel
|
||||||
import net.woggioni.rbcs.api.CacheHandlerFactory
|
|
||||||
import net.woggioni.rbcs.api.Configuration
|
|
||||||
import net.woggioni.rbcs.common.HostAndPort
|
|
||||||
import net.woggioni.rbcs.common.createLogger
|
|
||||||
import net.woggioni.rbcs.server.memcache.client.MemcacheClient
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import net.woggioni.rbcs.api.CacheHandler
|
||||||
|
import net.woggioni.rbcs.api.CacheHandlerFactory
|
||||||
|
import net.woggioni.rbcs.api.Configuration
|
||||||
|
import net.woggioni.rbcs.common.HostAndPort
|
||||||
|
import net.woggioni.rbcs.common.createLogger
|
||||||
|
import net.woggioni.rbcs.server.memcache.client.MemcacheClient
|
||||||
|
|
||||||
data class MemcacheCacheConfiguration(
|
data class MemcacheCacheConfiguration(
|
||||||
val servers: List<Server>,
|
val servers: List<Server>,
|
||||||
val maxAge: Duration = Duration.ofDays(1),
|
val maxAge: Duration = Duration.ofDays(1),
|
||||||
|
val keyPrefix : String? = null,
|
||||||
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,22 +48,24 @@ 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 {
|
): CacheHandler {
|
||||||
return MemcacheCacheHandler(
|
return MemcacheCacheHandler(
|
||||||
MemcacheClient(
|
MemcacheClient(
|
||||||
this@MemcacheCacheConfiguration.servers,
|
this@MemcacheCacheConfiguration.servers,
|
||||||
chunkSize,
|
cfg.connection.chunkSize,
|
||||||
eventLoop,
|
eventLoop,
|
||||||
socketChannelFactory,
|
socketChannelFactory,
|
||||||
connectionPoolMap
|
connectionPoolMap
|
||||||
),
|
),
|
||||||
|
keyPrefix,
|
||||||
digestAlgorithm,
|
digestAlgorithm,
|
||||||
compressionMode != null,
|
compressionMode != null,
|
||||||
compressionLevel,
|
compressionLevel,
|
||||||
chunkSize,
|
cfg.connection.chunkSize,
|
||||||
maxAge
|
maxAge
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,8 @@ package net.woggioni.rbcs.server.memcache
|
|||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.buffer.ByteBufAllocator
|
import io.netty.buffer.ByteBufAllocator
|
||||||
import io.netty.buffer.CompositeByteBuf
|
import io.netty.buffer.CompositeByteBuf
|
||||||
|
import io.netty.channel.Channel as NettyChannel
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
|
||||||
import io.netty.handler.codec.memcache.DefaultLastMemcacheContent
|
import io.netty.handler.codec.memcache.DefaultLastMemcacheContent
|
||||||
import io.netty.handler.codec.memcache.DefaultMemcacheContent
|
import io.netty.handler.codec.memcache.DefaultMemcacheContent
|
||||||
import io.netty.handler.codec.memcache.LastMemcacheContent
|
import io.netty.handler.codec.memcache.LastMemcacheContent
|
||||||
@@ -13,6 +13,22 @@ import io.netty.handler.codec.memcache.binary.BinaryMemcacheOpcodes
|
|||||||
import io.netty.handler.codec.memcache.binary.BinaryMemcacheResponse
|
import io.netty.handler.codec.memcache.binary.BinaryMemcacheResponse
|
||||||
import io.netty.handler.codec.memcache.binary.BinaryMemcacheResponseStatus
|
import io.netty.handler.codec.memcache.binary.BinaryMemcacheResponseStatus
|
||||||
import io.netty.handler.codec.memcache.binary.DefaultBinaryMemcacheRequest
|
import io.netty.handler.codec.memcache.binary.DefaultBinaryMemcacheRequest
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
import java.io.ObjectOutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.channels.Channels
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
import java.nio.channels.ReadableByteChannel
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.zip.Deflater
|
||||||
|
import java.util.zip.DeflaterOutputStream
|
||||||
|
import java.util.zip.InflaterOutputStream
|
||||||
|
import net.woggioni.rbcs.api.CacheHandler
|
||||||
import net.woggioni.rbcs.api.CacheValueMetadata
|
import net.woggioni.rbcs.api.CacheValueMetadata
|
||||||
import net.woggioni.rbcs.api.exception.ContentTooLargeException
|
import net.woggioni.rbcs.api.exception.ContentTooLargeException
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage
|
import net.woggioni.rbcs.api.message.CacheMessage
|
||||||
@@ -34,31 +50,16 @@ import net.woggioni.rbcs.common.trace
|
|||||||
import net.woggioni.rbcs.server.memcache.client.MemcacheClient
|
import net.woggioni.rbcs.server.memcache.client.MemcacheClient
|
||||||
import net.woggioni.rbcs.server.memcache.client.MemcacheRequestController
|
import net.woggioni.rbcs.server.memcache.client.MemcacheRequestController
|
||||||
import net.woggioni.rbcs.server.memcache.client.MemcacheResponseHandler
|
import net.woggioni.rbcs.server.memcache.client.MemcacheResponseHandler
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.ObjectInputStream
|
|
||||||
import java.io.ObjectOutputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.channels.Channels
|
|
||||||
import java.nio.channels.FileChannel
|
|
||||||
import java.nio.channels.ReadableByteChannel
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.StandardOpenOption
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
import java.util.zip.DeflaterOutputStream
|
|
||||||
import java.util.zip.InflaterOutputStream
|
|
||||||
import io.netty.channel.Channel as NettyChannel
|
|
||||||
|
|
||||||
class MemcacheCacheHandler(
|
class MemcacheCacheHandler(
|
||||||
private val client: MemcacheClient,
|
private val client: MemcacheClient,
|
||||||
|
private val keyPrefix: String?,
|
||||||
private val digestAlgorithm: String?,
|
private val digestAlgorithm: String?,
|
||||||
private val compressionEnabled: Boolean,
|
private val compressionEnabled: Boolean,
|
||||||
private val compressionLevel: Int,
|
private val compressionLevel: Int,
|
||||||
private val chunkSize: Int,
|
private val chunkSize: Int,
|
||||||
private val maxAge: Duration
|
private val maxAge: Duration
|
||||||
) : SimpleChannelInboundHandler<CacheMessage>() {
|
) : CacheHandler() {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = createLogger<MemcacheCacheHandler>()
|
private val log = createLogger<MemcacheCacheHandler>()
|
||||||
|
|
||||||
@@ -69,10 +70,14 @@ class MemcacheCacheHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface InProgressRequest {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private inner class InProgressGetRequest(
|
private inner class InProgressGetRequest(
|
||||||
private val key: String,
|
val key: String,
|
||||||
private val ctx: ChannelHandlerContext
|
private val ctx: ChannelHandlerContext
|
||||||
) {
|
) : InProgressRequest {
|
||||||
private val acc = ctx.alloc().compositeBuffer()
|
private val acc = ctx.alloc().compositeBuffer()
|
||||||
private val chunk = ctx.alloc().compositeBuffer()
|
private val chunk = ctx.alloc().compositeBuffer()
|
||||||
private val outputStream = ByteBufOutputStream(chunk).let {
|
private val outputStream = ByteBufOutputStream(chunk).let {
|
||||||
@@ -98,32 +103,35 @@ class MemcacheCacheHandler(
|
|||||||
acc.retain()
|
acc.retain()
|
||||||
it.readObject() as CacheValueMetadata
|
it.readObject() as CacheValueMetadata
|
||||||
}
|
}
|
||||||
ctx.writeAndFlush(CacheValueFoundResponse(key, metadata))
|
log.trace(ctx) {
|
||||||
|
"Sending response from cache"
|
||||||
|
}
|
||||||
|
sendMessageAndFlush(ctx, CacheValueFoundResponse(key, metadata))
|
||||||
responseSent = true
|
responseSent = true
|
||||||
acc.readerIndex(Int.SIZE_BYTES + mSize)
|
acc.readerIndex(Int.SIZE_BYTES + mSize)
|
||||||
}
|
}
|
||||||
if (responseSent) {
|
if (responseSent) {
|
||||||
acc.readBytes(outputStream, acc.readableBytes())
|
acc.readBytes(outputStream, acc.readableBytes())
|
||||||
if(acc.readableBytes() >= chunkSize) {
|
if (acc.readableBytes() >= chunkSize) {
|
||||||
flush(false)
|
flush(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun flush(last : Boolean) {
|
private fun flush(last: Boolean) {
|
||||||
val toSend = extractChunk(chunk, ctx.alloc())
|
val toSend = extractChunk(chunk, ctx.alloc())
|
||||||
val msg = if(last) {
|
val msg = if (last) {
|
||||||
log.trace(ctx) {
|
log.trace(ctx) {
|
||||||
"Sending last chunk to client on channel ${ctx.channel().id().asShortText()}"
|
"Sending last chunk to client"
|
||||||
}
|
}
|
||||||
LastCacheContent(toSend)
|
LastCacheContent(toSend)
|
||||||
} else {
|
} else {
|
||||||
log.trace(ctx) {
|
log.trace(ctx) {
|
||||||
"Sending chunk to client on channel ${ctx.channel().id().asShortText()}"
|
"Sending chunk to client"
|
||||||
}
|
}
|
||||||
CacheContent(toSend)
|
CacheContent(toSend)
|
||||||
}
|
}
|
||||||
ctx.writeAndFlush(msg)
|
sendMessageAndFlush(ctx, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun commit() {
|
fun commit() {
|
||||||
@@ -141,14 +149,14 @@ class MemcacheCacheHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private inner class InProgressPutRequest(
|
private inner class InProgressPutRequest(
|
||||||
private val ch : NettyChannel,
|
private val ch: NettyChannel,
|
||||||
metadata : CacheValueMetadata,
|
metadata: CacheValueMetadata,
|
||||||
val digest : ByteBuf,
|
val digest: ByteBuf,
|
||||||
val requestController: CompletableFuture<MemcacheRequestController>,
|
val requestController: CompletableFuture<MemcacheRequestController>,
|
||||||
private val alloc: ByteBufAllocator
|
private val alloc: ByteBufAllocator
|
||||||
) {
|
) : InProgressRequest {
|
||||||
private var totalSize = 0
|
private var totalSize = 0
|
||||||
private var tmpFile : FileChannel? = null
|
private var tmpFile: FileChannel? = null
|
||||||
private val accumulator = alloc.compositeBuffer()
|
private val accumulator = alloc.compositeBuffer()
|
||||||
private val stream = ByteBufOutputStream(accumulator).let {
|
private val stream = ByteBufOutputStream(accumulator).let {
|
||||||
if (compressionEnabled) {
|
if (compressionEnabled) {
|
||||||
@@ -175,7 +183,7 @@ class MemcacheCacheHandler(
|
|||||||
tmpFile?.let {
|
tmpFile?.let {
|
||||||
flushToDisk(it, accumulator)
|
flushToDisk(it, accumulator)
|
||||||
}
|
}
|
||||||
if(accumulator.readableBytes() > 0x100000) {
|
if (accumulator.readableBytes() > 0x100000) {
|
||||||
log.debug(ch) {
|
log.debug(ch) {
|
||||||
"Entry is too big, buffering it into a file"
|
"Entry is too big, buffering it into a file"
|
||||||
}
|
}
|
||||||
@@ -192,18 +200,18 @@ class MemcacheCacheHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun flushToDisk(fc : FileChannel, buf : CompositeByteBuf) {
|
private fun flushToDisk(fc: FileChannel, buf: CompositeByteBuf) {
|
||||||
val chunk = extractChunk(buf, alloc)
|
val chunk = extractChunk(buf, alloc)
|
||||||
fc.write(chunk.nioBuffer())
|
fc.write(chunk.nioBuffer())
|
||||||
chunk.release()
|
chunk.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun commit() : Pair<Int, ReadableByteChannel> {
|
fun commit(): Pair<Int, ReadableByteChannel> {
|
||||||
digest.release()
|
digest.release()
|
||||||
accumulator.retain()
|
accumulator.retain()
|
||||||
stream.close()
|
stream.close()
|
||||||
val fileChannel = tmpFile
|
val fileChannel = tmpFile
|
||||||
return if(fileChannel != null) {
|
return if (fileChannel != null) {
|
||||||
flushToDisk(fileChannel, accumulator)
|
flushToDisk(fileChannel, accumulator)
|
||||||
accumulator.release()
|
accumulator.release()
|
||||||
fileChannel.position(0)
|
fileChannel.position(0)
|
||||||
@@ -224,8 +232,7 @@ class MemcacheCacheHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var inProgressPutRequest: InProgressPutRequest? = null
|
private var inProgressRequest: InProgressRequest? = null
|
||||||
private var inProgressGetRequest: InProgressGetRequest? = null
|
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: CacheMessage) {
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: CacheMessage) {
|
||||||
when (msg) {
|
when (msg) {
|
||||||
@@ -242,7 +249,7 @@ class MemcacheCacheHandler(
|
|||||||
"Fetching ${msg.key} from memcache"
|
"Fetching ${msg.key} from memcache"
|
||||||
}
|
}
|
||||||
val key = ctx.alloc().buffer().also {
|
val key = ctx.alloc().buffer().also {
|
||||||
it.writeBytes(processCacheKey(msg.key, digestAlgorithm))
|
it.writeBytes(processCacheKey(msg.key, keyPrefix, digestAlgorithm))
|
||||||
}
|
}
|
||||||
val responseHandler = object : MemcacheResponseHandler {
|
val responseHandler = object : MemcacheResponseHandler {
|
||||||
override fun responseReceived(response: BinaryMemcacheResponse) {
|
override fun responseReceived(response: BinaryMemcacheResponse) {
|
||||||
@@ -252,32 +259,39 @@ class MemcacheCacheHandler(
|
|||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Cache hit for key ${msg.key} on memcache"
|
"Cache hit for key ${msg.key} on memcache"
|
||||||
}
|
}
|
||||||
inProgressGetRequest = InProgressGetRequest(msg.key, ctx)
|
inProgressRequest = InProgressGetRequest(msg.key, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
BinaryMemcacheResponseStatus.KEY_ENOENT -> {
|
BinaryMemcacheResponseStatus.KEY_ENOENT -> {
|
||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Cache miss for key ${msg.key} on memcache"
|
"Cache miss for key ${msg.key} on memcache"
|
||||||
}
|
}
|
||||||
ctx.writeAndFlush(CacheValueNotFoundResponse())
|
sendMessageAndFlush(ctx, CacheValueNotFoundResponse(msg.key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentReceived(content: MemcacheContent) {
|
override fun contentReceived(content: MemcacheContent) {
|
||||||
log.trace(ctx) {
|
log.trace(ctx) {
|
||||||
"${if(content is LastMemcacheContent) "Last chunk" else "Chunk"} of ${content.content().readableBytes()} bytes received from memcache for key ${msg.key}"
|
"${if (content is LastMemcacheContent) "Last chunk" else "Chunk"} of ${
|
||||||
|
content.content().readableBytes()
|
||||||
|
} bytes received from memcache for key ${msg.key}"
|
||||||
}
|
}
|
||||||
inProgressGetRequest?.write(content.content())
|
(inProgressRequest as? InProgressGetRequest)?.let { inProgressGetRequest ->
|
||||||
if (content is LastMemcacheContent) {
|
inProgressGetRequest.write(content.content())
|
||||||
inProgressGetRequest?.commit()
|
if (content is LastMemcacheContent) {
|
||||||
|
inProgressRequest = null
|
||||||
|
inProgressGetRequest.commit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exceptionCaught(ex: Throwable) {
|
override fun exceptionCaught(ex: Throwable) {
|
||||||
inProgressGetRequest?.let {
|
(inProgressRequest as? InProgressGetRequest).let { inProgressGetRequest ->
|
||||||
inProgressGetRequest = null
|
inProgressGetRequest?.let {
|
||||||
it.rollback()
|
inProgressRequest = null
|
||||||
|
it.rollback()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this@MemcacheCacheHandler.exceptionCaught(ctx, ex)
|
this@MemcacheCacheHandler.exceptionCaught(ctx, ex)
|
||||||
}
|
}
|
||||||
@@ -290,12 +304,13 @@ class MemcacheCacheHandler(
|
|||||||
setOpcode(BinaryMemcacheOpcodes.GET)
|
setOpcode(BinaryMemcacheOpcodes.GET)
|
||||||
}
|
}
|
||||||
requestHandle.sendRequest(request)
|
requestHandle.sendRequest(request)
|
||||||
|
requestHandle.sendContent(LastMemcacheContent.EMPTY_LAST_CONTENT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePutRequest(ctx: ChannelHandlerContext, msg: CachePutRequest) {
|
private fun handlePutRequest(ctx: ChannelHandlerContext, msg: CachePutRequest) {
|
||||||
val key = ctx.alloc().buffer().also {
|
val key = ctx.alloc().buffer().also {
|
||||||
it.writeBytes(processCacheKey(msg.key, digestAlgorithm))
|
it.writeBytes(processCacheKey(msg.key, keyPrefix, digestAlgorithm))
|
||||||
}
|
}
|
||||||
val responseHandler = object : MemcacheResponseHandler {
|
val responseHandler = object : MemcacheResponseHandler {
|
||||||
override fun responseReceived(response: BinaryMemcacheResponse) {
|
override fun responseReceived(response: BinaryMemcacheResponse) {
|
||||||
@@ -305,8 +320,9 @@ class MemcacheCacheHandler(
|
|||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Inserted key ${msg.key} into memcache"
|
"Inserted key ${msg.key} into memcache"
|
||||||
}
|
}
|
||||||
ctx.writeAndFlush(CachePutResponse(msg.key))
|
sendMessageAndFlush(ctx, CachePutResponse(msg.key))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> this@MemcacheCacheHandler.exceptionCaught(ctx, MemcacheException(status))
|
else -> this@MemcacheCacheHandler.exceptionCaught(ctx, MemcacheException(status))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,86 +339,103 @@ class MemcacheCacheHandler(
|
|||||||
this@MemcacheCacheHandler.exceptionCaught(ctx, ex)
|
this@MemcacheCacheHandler.exceptionCaught(ctx, ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inProgressPutRequest = InProgressPutRequest(ctx.channel(), msg.metadata, key, requestController, ctx.alloc())
|
inProgressRequest = InProgressPutRequest(ctx.channel(), msg.metadata, key, requestController, ctx.alloc())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCacheContent(ctx: ChannelHandlerContext, msg: CacheContent) {
|
private fun handleCacheContent(ctx: ChannelHandlerContext, msg: CacheContent) {
|
||||||
inProgressPutRequest?.let { request ->
|
val request = inProgressRequest
|
||||||
log.trace(ctx) {
|
when (request) {
|
||||||
"Received chunk of ${msg.content().readableBytes()} bytes for memcache"
|
is InProgressPutRequest -> {
|
||||||
|
log.trace(ctx) {
|
||||||
|
"Received chunk of ${msg.content().readableBytes()} bytes for memcache"
|
||||||
|
}
|
||||||
|
request.write(msg.content())
|
||||||
|
}
|
||||||
|
|
||||||
|
is InProgressGetRequest -> {
|
||||||
|
msg.release()
|
||||||
}
|
}
|
||||||
request.write(msg.content())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLastCacheContent(ctx: ChannelHandlerContext, msg: LastCacheContent) {
|
private fun handleLastCacheContent(ctx: ChannelHandlerContext, msg: LastCacheContent) {
|
||||||
inProgressPutRequest?.let { request ->
|
val request = inProgressRequest
|
||||||
inProgressPutRequest = null
|
when (request) {
|
||||||
log.trace(ctx) {
|
is InProgressPutRequest -> {
|
||||||
"Received last chunk of ${msg.content().readableBytes()} bytes for memcache"
|
inProgressRequest = null
|
||||||
}
|
log.trace(ctx) {
|
||||||
request.write(msg.content())
|
"Received last chunk of ${msg.content().readableBytes()} bytes for memcache"
|
||||||
val key = request.digest.retainedDuplicate()
|
}
|
||||||
val (payloadSize, payloadSource) = request.commit()
|
request.write(msg.content())
|
||||||
val extras = ctx.alloc().buffer(8, 8)
|
val key = request.digest.retainedDuplicate()
|
||||||
extras.writeInt(0)
|
val (payloadSize, payloadSource) = request.commit()
|
||||||
extras.writeInt(encodeExpiry(maxAge))
|
val extras = ctx.alloc().buffer(8, 8)
|
||||||
val totalBodyLength = request.digest.readableBytes() + extras.readableBytes() + payloadSize
|
extras.writeInt(0)
|
||||||
request.requestController.whenComplete { requestController, ex ->
|
extras.writeInt(encodeExpiry(maxAge))
|
||||||
if(ex == null) {
|
val totalBodyLength = request.digest.readableBytes() + extras.readableBytes() + payloadSize
|
||||||
log.trace(ctx) {
|
log.trace(ctx) {
|
||||||
"Sending SET request to memcache"
|
"Trying to send SET request to memcache"
|
||||||
}
|
}
|
||||||
requestController.sendRequest(DefaultBinaryMemcacheRequest().apply {
|
request.requestController.whenComplete { requestController, ex ->
|
||||||
setOpcode(BinaryMemcacheOpcodes.SET)
|
if (ex == null) {
|
||||||
setKey(key)
|
log.trace(ctx) {
|
||||||
setExtras(extras)
|
"Sending SET request to memcache"
|
||||||
setTotalBodyLength(totalBodyLength)
|
}
|
||||||
})
|
requestController.sendRequest(DefaultBinaryMemcacheRequest().apply {
|
||||||
log.trace(ctx) {
|
setOpcode(BinaryMemcacheOpcodes.SET)
|
||||||
"Sending request payload to memcache"
|
setKey(key)
|
||||||
}
|
setExtras(extras)
|
||||||
payloadSource.use { source ->
|
setTotalBodyLength(totalBodyLength)
|
||||||
val bb = ByteBuffer.allocate(chunkSize)
|
})
|
||||||
while (true) {
|
log.trace(ctx) {
|
||||||
val read = source.read(bb)
|
"Sending request payload to memcache"
|
||||||
bb.limit()
|
}
|
||||||
if(read >= 0 && bb.position() < chunkSize && bb.hasRemaining()) {
|
payloadSource.use { source ->
|
||||||
continue
|
val bb = ByteBuffer.allocate(chunkSize)
|
||||||
}
|
while (true) {
|
||||||
val chunk = ctx.alloc().buffer(chunkSize)
|
val read = source.read(bb)
|
||||||
bb.flip()
|
bb.limit()
|
||||||
chunk.writeBytes(bb)
|
if (read >= 0 && bb.position() < chunkSize && bb.hasRemaining()) {
|
||||||
bb.clear()
|
continue
|
||||||
log.trace(ctx) {
|
}
|
||||||
"Sending ${chunk.readableBytes()} bytes chunk to memcache"
|
val chunk = ctx.alloc().buffer(chunkSize)
|
||||||
}
|
bb.flip()
|
||||||
if(read < 0) {
|
chunk.writeBytes(bb)
|
||||||
requestController.sendContent(DefaultLastMemcacheContent(chunk))
|
bb.clear()
|
||||||
break
|
log.trace(ctx) {
|
||||||
} else {
|
"Sending ${chunk.readableBytes()} bytes chunk to memcache"
|
||||||
requestController.sendContent(DefaultMemcacheContent(chunk))
|
}
|
||||||
|
if (read < 0) {
|
||||||
|
requestController.sendContent(DefaultLastMemcacheContent(chunk))
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
requestController.sendContent(DefaultMemcacheContent(chunk))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
payloadSource.close()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
payloadSource.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
inProgressGetRequest?.let {
|
val request = inProgressRequest
|
||||||
inProgressGetRequest = null
|
when (request) {
|
||||||
it.rollback()
|
is InProgressPutRequest -> {
|
||||||
}
|
inProgressRequest = null
|
||||||
inProgressPutRequest?.let {
|
request.requestController.thenAccept { controller ->
|
||||||
inProgressPutRequest = null
|
controller.exceptionCaught(cause)
|
||||||
it.requestController.thenAccept { controller ->
|
}
|
||||||
controller.exceptionCaught(cause)
|
request.rollback()
|
||||||
|
}
|
||||||
|
|
||||||
|
is InProgressGetRequest -> {
|
||||||
|
inProgressRequest = null
|
||||||
|
request.rollback()
|
||||||
}
|
}
|
||||||
it.rollback()
|
|
||||||
}
|
}
|
||||||
super.exceptionCaught(ctx, cause)
|
super.exceptionCaught(ctx, cause)
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package net.woggioni.rbcs.server.memcache
|
package net.woggioni.rbcs.server.memcache
|
||||||
|
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import net.woggioni.rbcs.api.CacheProvider
|
import net.woggioni.rbcs.api.CacheProvider
|
||||||
import net.woggioni.rbcs.api.exception.ConfigurationException
|
import net.woggioni.rbcs.api.exception.ConfigurationException
|
||||||
import net.woggioni.rbcs.common.HostAndPort
|
import net.woggioni.rbcs.common.HostAndPort
|
||||||
@@ -9,8 +11,6 @@ import net.woggioni.rbcs.common.Xml.Companion.asIterable
|
|||||||
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import java.time.Duration
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
|
|
||||||
|
|
||||||
class MemcacheCacheProvider : CacheProvider<MemcacheCacheConfiguration> {
|
class MemcacheCacheProvider : CacheProvider<MemcacheCacheConfiguration> {
|
||||||
@@ -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
|
||||||
@@ -41,6 +38,7 @@ class MemcacheCacheProvider : CacheProvider<MemcacheCacheConfiguration> {
|
|||||||
else -> MemcacheCacheConfiguration.CompressionMode.DEFLATE
|
else -> MemcacheCacheConfiguration.CompressionMode.DEFLATE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val keyPrefix = el.renderAttribute("key-prefix")
|
||||||
val digestAlgorithm = el.renderAttribute("digest")
|
val digestAlgorithm = el.renderAttribute("digest")
|
||||||
for (child in el.asIterable()) {
|
for (child in el.asIterable()) {
|
||||||
when (child.nodeName) {
|
when (child.nodeName) {
|
||||||
@@ -57,14 +55,13 @@ class MemcacheCacheProvider : CacheProvider<MemcacheCacheConfiguration> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return MemcacheCacheConfiguration(
|
return MemcacheCacheConfiguration(
|
||||||
servers,
|
servers,
|
||||||
maxAge,
|
maxAge,
|
||||||
|
keyPrefix,
|
||||||
digestAlgorithm,
|
digestAlgorithm,
|
||||||
compressionMode,
|
compressionMode,
|
||||||
compressionLevel,
|
compressionLevel
|
||||||
chunkSize
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +79,12 @@ class MemcacheCacheProvider : CacheProvider<MemcacheCacheConfiguration> {
|
|||||||
}
|
}
|
||||||
attr("max-connections", server.maxConnections.toString())
|
attr("max-connections", server.maxConnections.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
attr("max-age", maxAge.toString())
|
attr("max-age", maxAge.toString())
|
||||||
attr("chunk-size", chunkSize.toString())
|
keyPrefix?.let {
|
||||||
|
attr("key-prefix", it)
|
||||||
|
}
|
||||||
digestAlgorithm?.let { digestAlgorithm ->
|
digestAlgorithm?.let { digestAlgorithm ->
|
||||||
attr("digest", digestAlgorithm)
|
attr("digest", digestAlgorithm)
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,6 @@ import io.netty.channel.ChannelPipeline
|
|||||||
import io.netty.channel.EventLoopGroup
|
import io.netty.channel.EventLoopGroup
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
import io.netty.channel.pool.AbstractChannelPoolHandler
|
import io.netty.channel.pool.AbstractChannelPoolHandler
|
||||||
import io.netty.channel.pool.ChannelPool
|
|
||||||
import io.netty.channel.pool.FixedChannelPool
|
import io.netty.channel.pool.FixedChannelPool
|
||||||
import io.netty.channel.socket.SocketChannel
|
import io.netty.channel.socket.SocketChannel
|
||||||
import io.netty.handler.codec.memcache.LastMemcacheContent
|
import io.netty.handler.codec.memcache.LastMemcacheContent
|
||||||
@@ -21,17 +20,17 @@ import io.netty.handler.codec.memcache.MemcacheObject
|
|||||||
import io.netty.handler.codec.memcache.binary.BinaryMemcacheClientCodec
|
import io.netty.handler.codec.memcache.binary.BinaryMemcacheClientCodec
|
||||||
import io.netty.handler.codec.memcache.binary.BinaryMemcacheRequest
|
import io.netty.handler.codec.memcache.binary.BinaryMemcacheRequest
|
||||||
import io.netty.handler.codec.memcache.binary.BinaryMemcacheResponse
|
import io.netty.handler.codec.memcache.binary.BinaryMemcacheResponse
|
||||||
|
import io.netty.util.concurrent.Future as NettyFuture
|
||||||
import io.netty.util.concurrent.GenericFutureListener
|
import io.netty.util.concurrent.GenericFutureListener
|
||||||
import net.woggioni.rbcs.common.HostAndPort
|
|
||||||
import net.woggioni.rbcs.common.createLogger
|
|
||||||
import net.woggioni.rbcs.common.warn
|
|
||||||
import net.woggioni.rbcs.server.memcache.MemcacheCacheConfiguration
|
|
||||||
import net.woggioni.rbcs.server.memcache.MemcacheCacheHandler
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import io.netty.util.concurrent.Future as NettyFuture
|
import net.woggioni.rbcs.common.HostAndPort
|
||||||
|
import net.woggioni.rbcs.common.createLogger
|
||||||
|
import net.woggioni.rbcs.common.trace
|
||||||
|
import net.woggioni.rbcs.server.memcache.MemcacheCacheConfiguration
|
||||||
|
import net.woggioni.rbcs.server.memcache.MemcacheCacheHandler
|
||||||
|
|
||||||
|
|
||||||
class MemcacheClient(
|
class MemcacheClient(
|
||||||
@@ -94,18 +93,6 @@ class MemcacheClient(
|
|||||||
pool.acquire().addListener(object : GenericFutureListener<NettyFuture<Channel>> {
|
pool.acquire().addListener(object : GenericFutureListener<NettyFuture<Channel>> {
|
||||||
override fun operationComplete(channelFuture: NettyFuture<Channel>) {
|
override fun operationComplete(channelFuture: NettyFuture<Channel>) {
|
||||||
if (channelFuture.isSuccess) {
|
if (channelFuture.isSuccess) {
|
||||||
|
|
||||||
var requestSent = false
|
|
||||||
var requestBodySent = false
|
|
||||||
var requestFinished = false
|
|
||||||
var responseReceived = false
|
|
||||||
var responseBodyReceived = false
|
|
||||||
var responseFinished = false
|
|
||||||
var requestBodySize = 0
|
|
||||||
var requestBodyBytesSent = 0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val channel = channelFuture.now
|
val channel = channelFuture.now
|
||||||
var connectionClosedByTheRemoteServer = true
|
var connectionClosedByTheRemoteServer = true
|
||||||
val closeCallback = {
|
val closeCallback = {
|
||||||
@@ -113,14 +100,7 @@ class MemcacheClient(
|
|||||||
val ex = IOException("The memcache server closed the connection")
|
val ex = IOException("The memcache server closed the connection")
|
||||||
val completed = response.completeExceptionally(ex)
|
val completed = response.completeExceptionally(ex)
|
||||||
if(!completed) responseHandler.exceptionCaught(ex)
|
if(!completed) responseHandler.exceptionCaught(ex)
|
||||||
log.warn {
|
|
||||||
"RequestSent: $requestSent, RequestBodySent: $requestBodySent, " +
|
|
||||||
"RequestFinished: $requestFinished, ResponseReceived: $responseReceived, " +
|
|
||||||
"ResponseBodyReceived: $responseBodyReceived, ResponseFinished: $responseFinished, " +
|
|
||||||
"RequestBodySize: $requestBodySize, RequestBodyBytesSent: $requestBodyBytesSent"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pool.release(channel)
|
|
||||||
}
|
}
|
||||||
val closeListener = ChannelFutureListener {
|
val closeListener = ChannelFutureListener {
|
||||||
closeCallback()
|
closeCallback()
|
||||||
@@ -140,18 +120,14 @@ class MemcacheClient(
|
|||||||
when (msg) {
|
when (msg) {
|
||||||
is BinaryMemcacheResponse -> {
|
is BinaryMemcacheResponse -> {
|
||||||
responseHandler.responseReceived(msg)
|
responseHandler.responseReceived(msg)
|
||||||
responseReceived = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is LastMemcacheContent -> {
|
is LastMemcacheContent -> {
|
||||||
responseFinished = true
|
|
||||||
responseHandler.contentReceived(msg)
|
responseHandler.contentReceived(msg)
|
||||||
pipeline.remove(this)
|
pipeline.remove(this)
|
||||||
pool.release(channel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is MemcacheContent -> {
|
is MemcacheContent -> {
|
||||||
responseBodyReceived = true
|
|
||||||
responseHandler.contentReceived(msg)
|
responseHandler.contentReceived(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,35 +141,43 @@ class MemcacheClient(
|
|||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
connectionClosedByTheRemoteServer = false
|
connectionClosedByTheRemoteServer = false
|
||||||
ctx.close()
|
ctx.close()
|
||||||
pool.release(channel)
|
|
||||||
responseHandler.exceptionCaught(cause)
|
responseHandler.exceptionCaught(cause)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.pipeline()
|
channel.pipeline().addLast(handler)
|
||||||
.addLast("client-handler", handler)
|
|
||||||
response.complete(object : MemcacheRequestController {
|
response.complete(object : MemcacheRequestController {
|
||||||
|
private var channelReleased = false
|
||||||
|
|
||||||
override fun sendRequest(request: BinaryMemcacheRequest) {
|
override fun sendRequest(request: BinaryMemcacheRequest) {
|
||||||
requestBodySize = request.totalBodyLength() - request.keyLength() - request.extrasLength()
|
|
||||||
channel.writeAndFlush(request)
|
channel.writeAndFlush(request)
|
||||||
requestSent = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendContent(content: MemcacheContent) {
|
override fun sendContent(content: MemcacheContent) {
|
||||||
val size = content.content().readableBytes()
|
|
||||||
channel.writeAndFlush(content).addListener {
|
channel.writeAndFlush(content).addListener {
|
||||||
requestBodyBytesSent += size
|
|
||||||
requestBodySent = true
|
|
||||||
if(content is LastMemcacheContent) {
|
if(content is LastMemcacheContent) {
|
||||||
requestFinished = true
|
if(!channelReleased) {
|
||||||
|
pool.release(channel)
|
||||||
|
channelReleased = true
|
||||||
|
log.trace(channel) {
|
||||||
|
"Channel released"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exceptionCaught(ex: Throwable) {
|
override fun exceptionCaught(ex: Throwable) {
|
||||||
|
log.warn(ex.message, ex)
|
||||||
connectionClosedByTheRemoteServer = false
|
connectionClosedByTheRemoteServer = false
|
||||||
channel.close()
|
channel.close()
|
||||||
|
if(!channelReleased) {
|
||||||
|
pool.release(channel)
|
||||||
|
channelReleased = true
|
||||||
|
log.trace(channel) {
|
||||||
|
"Channel released"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@@ -21,6 +21,14 @@
|
|||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
<xs:attribute name="max-age" type="xs:duration" default="P1D"/>
|
<xs:attribute name="max-age" type="xs:duration" default="P1D"/>
|
||||||
<xs:attribute name="chunk-size" type="rbcs:byteSizeType" default="0x10000"/>
|
<xs:attribute name="chunk-size" type="rbcs:byteSizeType" default="0x10000"/>
|
||||||
|
<xs:attribute name="key-prefix" type="xs:string" use="optional">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Prepend this string to all the keys inserted in memcache,
|
||||||
|
useful in case the caching backend is shared with other applications
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
<xs:attribute name="digest" type="xs:token"/>
|
<xs:attribute name="digest" type="xs:token"/>
|
||||||
<xs:attribute name="compression-mode" type="rbcs-memcache:compressionType"/>
|
<xs:attribute name="compression-mode" type="rbcs-memcache:compressionType"/>
|
||||||
<xs:attribute name="compression-level" type="rbcs:compressionLevelType" default="-1"/>
|
<xs:attribute name="compression-level" type="rbcs:compressionLevelType" default="-1"/>
|
||||||
|
@@ -2,12 +2,12 @@ package net.woggioni.rbcs.server.memcache.client
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBufUtil
|
import io.netty.buffer.ByteBufUtil
|
||||||
import io.netty.buffer.Unpooled
|
import io.netty.buffer.Unpooled
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.channels.Channels
|
import java.nio.channels.Channels
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import org.junit.jupiter.api.Assertions
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class ByteBufferTest {
|
class ByteBufferTest {
|
||||||
|
|
||||||
|
@@ -3,27 +3,27 @@ import net.woggioni.rbcs.server.cache.FileSystemCacheProvider;
|
|||||||
import net.woggioni.rbcs.server.cache.InMemoryCacheProvider;
|
import net.woggioni.rbcs.server.cache.InMemoryCacheProvider;
|
||||||
|
|
||||||
module net.woggioni.rbcs.server {
|
module net.woggioni.rbcs.server {
|
||||||
requires java.sql;
|
|
||||||
requires java.xml;
|
requires java.xml;
|
||||||
requires java.logging;
|
|
||||||
requires java.naming;
|
requires java.naming;
|
||||||
requires kotlin.stdlib;
|
requires kotlin.stdlib;
|
||||||
requires io.netty.buffer;
|
|
||||||
requires io.netty.transport;
|
|
||||||
requires io.netty.codec.http;
|
requires io.netty.codec.http;
|
||||||
requires io.netty.common;
|
|
||||||
requires io.netty.handler;
|
requires io.netty.handler;
|
||||||
requires io.netty.codec;
|
|
||||||
requires org.slf4j;
|
|
||||||
requires net.woggioni.jwo;
|
requires net.woggioni.jwo;
|
||||||
requires net.woggioni.rbcs.common;
|
requires net.woggioni.rbcs.common;
|
||||||
requires net.woggioni.rbcs.api;
|
requires net.woggioni.rbcs.api;
|
||||||
|
requires io.netty.codec.compression;
|
||||||
|
requires io.netty.transport;
|
||||||
|
requires io.netty.buffer;
|
||||||
|
requires io.netty.common;
|
||||||
|
requires io.netty.codec;
|
||||||
|
requires org.slf4j;
|
||||||
|
|
||||||
exports net.woggioni.rbcs.server;
|
exports 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;
|
||||||
}
|
}
|
@@ -11,7 +11,8 @@ import io.netty.channel.ChannelInboundHandlerAdapter
|
|||||||
import io.netty.channel.ChannelInitializer
|
import io.netty.channel.ChannelInitializer
|
||||||
import io.netty.channel.ChannelOption
|
import io.netty.channel.ChannelOption
|
||||||
import io.netty.channel.ChannelPromise
|
import io.netty.channel.ChannelPromise
|
||||||
import io.netty.channel.nio.NioEventLoopGroup
|
import io.netty.channel.MultiThreadIoEventLoopGroup
|
||||||
|
import io.netty.channel.nio.NioIoHandler
|
||||||
import io.netty.channel.socket.DatagramChannel
|
import io.netty.channel.socket.DatagramChannel
|
||||||
import io.netty.channel.socket.ServerSocketChannel
|
import io.netty.channel.socket.ServerSocketChannel
|
||||||
import io.netty.channel.socket.SocketChannel
|
import io.netty.channel.socket.SocketChannel
|
||||||
@@ -21,6 +22,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
|
||||||
@@ -33,8 +35,25 @@ import io.netty.handler.timeout.IdleState
|
|||||||
import io.netty.handler.timeout.IdleStateEvent
|
import io.netty.handler.timeout.IdleStateEvent
|
||||||
import io.netty.handler.timeout.IdleStateHandler
|
import io.netty.handler.timeout.IdleStateHandler
|
||||||
import io.netty.util.AttributeKey
|
import io.netty.util.AttributeKey
|
||||||
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup
|
import io.netty.util.concurrent.EventExecutorGroup
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.Arrays
|
||||||
|
import java.util.Base64
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
import java.util.regex.Matcher
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import javax.naming.ldap.LdapName
|
||||||
|
import javax.net.ssl.SSLPeerUnverifiedException
|
||||||
import net.woggioni.rbcs.api.AsyncCloseable
|
import net.woggioni.rbcs.api.AsyncCloseable
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.api.exception.ConfigurationException
|
import net.woggioni.rbcs.api.exception.ConfigurationException
|
||||||
@@ -54,28 +73,10 @@ import net.woggioni.rbcs.server.configuration.Parser
|
|||||||
import net.woggioni.rbcs.server.configuration.Serializer
|
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.ReadTriggerDuplexHandler
|
||||||
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.net.InetSocketAddress
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.security.PrivateKey
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.Arrays
|
|
||||||
import java.util.Base64
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.Future
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
import java.util.regex.Matcher
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
import javax.naming.ldap.LdapName
|
|
||||||
import javax.net.ssl.SSLPeerUnverifiedException
|
|
||||||
|
|
||||||
class RemoteBuildCacheServer(private val cfg: Configuration) {
|
class RemoteBuildCacheServer(private val cfg: Configuration) {
|
||||||
|
|
||||||
@@ -207,7 +208,6 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
private val cfg: Configuration,
|
private val cfg: Configuration,
|
||||||
private val channelFactory : ChannelFactory<SocketChannel>,
|
private val channelFactory : ChannelFactory<SocketChannel>,
|
||||||
private val datagramChannelFactory : ChannelFactory<DatagramChannel>,
|
private val datagramChannelFactory : ChannelFactory<DatagramChannel>,
|
||||||
private val eventExecutorGroup: EventExecutorGroup
|
|
||||||
) : ChannelInitializer<Channel>(), AsyncCloseable {
|
) : ChannelInitializer<Channel>(), AsyncCloseable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -298,6 +298,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
"Closed connection ${ch.id().asShortText()} with ${ch.remoteAddress()}"
|
"Closed connection ${ch.id().asShortText()} with ${ch.remoteAddress()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ch.config().isAutoRead = false
|
||||||
val pipeline = ch.pipeline()
|
val pipeline = ch.pipeline()
|
||||||
cfg.connection.also { conn ->
|
cfg.connection.also { conn ->
|
||||||
val readIdleTimeout = conn.readIdleTimeout.toMillis()
|
val readIdleTimeout = conn.readIdleTimeout.toMillis()
|
||||||
@@ -340,24 +341,27 @@ 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(ReadTriggerDuplexHandler.NAME, ReadTriggerDuplexHandler())
|
||||||
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())
|
||||||
authenticator?.let {
|
authenticator?.let {
|
||||||
pipeline.addLast(it)
|
pipeline.addLast(it)
|
||||||
}
|
}
|
||||||
pipeline.addLast(ThrottlingHandler(bucketManager, cfg.connection))
|
pipeline.addLast(ThrottlingHandler(bucketManager,cfg.rateLimiter, cfg.connection))
|
||||||
|
|
||||||
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(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()
|
||||||
@@ -436,20 +440,13 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
|
|
||||||
fun run(): ServerHandle {
|
fun run(): ServerHandle {
|
||||||
// Create the multithreaded event loops for the server
|
// Create the multithreaded event loops for the server
|
||||||
val bossGroup = NioEventLoopGroup(1)
|
val bossGroup = MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory())
|
||||||
val channelFactory = ChannelFactory<SocketChannel> { NioSocketChannel() }
|
val channelFactory = ChannelFactory<SocketChannel> { NioSocketChannel() }
|
||||||
val datagramChannelFactory = ChannelFactory<DatagramChannel> { NioDatagramChannel() }
|
val datagramChannelFactory = ChannelFactory<DatagramChannel> { NioDatagramChannel() }
|
||||||
val serverChannelFactory = ChannelFactory<ServerSocketChannel> { NioServerSocketChannel() }
|
val serverChannelFactory = ChannelFactory<ServerSocketChannel> { NioServerSocketChannel() }
|
||||||
val workerGroup = NioEventLoopGroup(0)
|
val workerGroup = MultiThreadIoEventLoopGroup(0, NioIoHandler.newFactory())
|
||||||
val eventExecutorGroup = run {
|
|
||||||
val threadFactory = if (cfg.eventExecutor.isUseVirtualThreads) {
|
val serverInitializer = ServerInitializer(cfg, channelFactory, datagramChannelFactory)
|
||||||
Thread.ofVirtual().factory()
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors(), threadFactory)
|
|
||||||
}
|
|
||||||
val serverInitializer = ServerInitializer(cfg, channelFactory, datagramChannelFactory, workerGroup)
|
|
||||||
val bootstrap = ServerBootstrap().apply {
|
val bootstrap = ServerBootstrap().apply {
|
||||||
// Configure the server
|
// Configure the server
|
||||||
group(bossGroup, workerGroup)
|
group(bossGroup, workerGroup)
|
||||||
@@ -470,7 +467,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
|
|||||||
return ServerHandle(
|
return ServerHandle(
|
||||||
httpChannel.closeFuture(),
|
httpChannel.closeFuture(),
|
||||||
bossGroup,
|
bossGroup,
|
||||||
setOf(workerGroup, eventExecutorGroup),
|
setOf(workerGroup),
|
||||||
serverInitializer
|
serverInitializer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,5 @@
|
|||||||
package net.woggioni.rbcs.server.cache
|
package net.woggioni.rbcs.server.cache
|
||||||
|
|
||||||
import net.woggioni.jwo.JWO
|
|
||||||
import net.woggioni.rbcs.api.AsyncCloseable
|
|
||||||
import net.woggioni.rbcs.api.CacheValueMetadata
|
|
||||||
import net.woggioni.rbcs.common.createLogger
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
@@ -20,6 +16,10 @@ import java.nio.file.attribute.BasicFileAttributes
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import net.woggioni.jwo.JWO
|
||||||
|
import net.woggioni.rbcs.api.AsyncCloseable
|
||||||
|
import net.woggioni.rbcs.api.CacheValueMetadata
|
||||||
|
import net.woggioni.rbcs.common.createLogger
|
||||||
|
|
||||||
class FileSystemCache(
|
class FileSystemCache(
|
||||||
val root: Path,
|
val root: Path,
|
||||||
|
@@ -4,12 +4,12 @@ import io.netty.channel.ChannelFactory
|
|||||||
import io.netty.channel.EventLoopGroup
|
import io.netty.channel.EventLoopGroup
|
||||||
import io.netty.channel.socket.DatagramChannel
|
import io.netty.channel.socket.DatagramChannel
|
||||||
import io.netty.channel.socket.SocketChannel
|
import io.netty.channel.socket.SocketChannel
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.time.Duration
|
||||||
import net.woggioni.jwo.Application
|
import net.woggioni.jwo.Application
|
||||||
import net.woggioni.rbcs.api.CacheHandlerFactory
|
import net.woggioni.rbcs.api.CacheHandlerFactory
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.common.RBCS
|
import net.woggioni.rbcs.common.RBCS
|
||||||
import java.nio.file.Path
|
|
||||||
import java.time.Duration
|
|
||||||
|
|
||||||
data class FileSystemCacheConfiguration(
|
data class FileSystemCacheConfiguration(
|
||||||
val root: Path?,
|
val root: Path?,
|
||||||
@@ -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
|
||||||
|
@@ -2,9 +2,14 @@ package net.woggioni.rbcs.server.cache
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
|
||||||
import io.netty.handler.codec.http.LastHttpContent
|
import io.netty.handler.codec.http.LastHttpContent
|
||||||
import io.netty.handler.stream.ChunkedNioFile
|
import io.netty.handler.stream.ChunkedNioFile
|
||||||
|
import java.nio.channels.Channels
|
||||||
|
import java.util.Base64
|
||||||
|
import java.util.zip.Deflater
|
||||||
|
import java.util.zip.DeflaterOutputStream
|
||||||
|
import java.util.zip.InflaterInputStream
|
||||||
|
import net.woggioni.rbcs.api.CacheHandler
|
||||||
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
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.CacheGetRequest
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheGetRequest
|
||||||
@@ -14,11 +19,6 @@ import net.woggioni.rbcs.api.message.CacheMessage.CacheValueFoundResponse
|
|||||||
import net.woggioni.rbcs.api.message.CacheMessage.CacheValueNotFoundResponse
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheValueNotFoundResponse
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
||||||
import net.woggioni.rbcs.common.RBCS.processCacheKey
|
import net.woggioni.rbcs.common.RBCS.processCacheKey
|
||||||
import java.nio.channels.Channels
|
|
||||||
import java.util.Base64
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
import java.util.zip.DeflaterOutputStream
|
|
||||||
import java.util.zip.InflaterInputStream
|
|
||||||
|
|
||||||
class FileSystemCacheHandler(
|
class FileSystemCacheHandler(
|
||||||
private val cache: FileSystemCache,
|
private val cache: FileSystemCache,
|
||||||
@@ -26,12 +26,18 @@ class FileSystemCacheHandler(
|
|||||||
private val compressionEnabled: Boolean,
|
private val compressionEnabled: Boolean,
|
||||||
private val compressionLevel: Int,
|
private val compressionLevel: Int,
|
||||||
private val chunkSize: Int
|
private val chunkSize: Int
|
||||||
) : SimpleChannelInboundHandler<CacheMessage>() {
|
) : CacheHandler() {
|
||||||
|
|
||||||
|
private interface InProgressRequest{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InProgressGetRequest(val request : CacheGetRequest) : InProgressRequest
|
||||||
|
|
||||||
private inner class InProgressPutRequest(
|
private inner class InProgressPutRequest(
|
||||||
val key : String,
|
val key : String,
|
||||||
private val fileSink : FileSystemCache.FileSink
|
private val fileSink : FileSystemCache.FileSink
|
||||||
) {
|
) : InProgressRequest {
|
||||||
|
|
||||||
private val stream = Channels.newOutputStream(fileSink.channel).let {
|
private val stream = Channels.newOutputStream(fileSink.channel).let {
|
||||||
if (compressionEnabled) {
|
if (compressionEnabled) {
|
||||||
@@ -55,7 +61,7 @@ class FileSystemCacheHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var inProgressPutRequest: InProgressPutRequest? = null
|
private var inProgressRequest: InProgressRequest? = null
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: CacheMessage) {
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: CacheMessage) {
|
||||||
when (msg) {
|
when (msg) {
|
||||||
@@ -68,55 +74,64 @@ class FileSystemCacheHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGetRequest(ctx: ChannelHandlerContext, msg: CacheGetRequest) {
|
private fun handleGetRequest(ctx: ChannelHandlerContext, msg: CacheGetRequest) {
|
||||||
val key = String(Base64.getUrlEncoder().encode(processCacheKey(msg.key, digestAlgorithm)))
|
inProgressRequest = InProgressGetRequest(msg)
|
||||||
cache.get(key)?.also { entryValue ->
|
|
||||||
ctx.writeAndFlush(CacheValueFoundResponse(msg.key, entryValue.metadata))
|
|
||||||
entryValue.channel.let { channel ->
|
|
||||||
if(compressionEnabled) {
|
|
||||||
InflaterInputStream(Channels.newInputStream(channel)).use { stream ->
|
|
||||||
|
|
||||||
outerLoop@
|
|
||||||
while (true) {
|
|
||||||
val buf = ctx.alloc().heapBuffer(chunkSize)
|
|
||||||
while(buf.readableBytes() < chunkSize) {
|
|
||||||
val read = buf.writeBytes(stream, chunkSize)
|
|
||||||
if(read < 0) {
|
|
||||||
ctx.writeAndFlush(LastCacheContent(buf))
|
|
||||||
break@outerLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.writeAndFlush(CacheContent(buf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.writeAndFlush(ChunkedNioFile(channel, entryValue.offset, entryValue.size - entryValue.offset, chunkSize))
|
|
||||||
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: ctx.writeAndFlush(CacheValueNotFoundResponse())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePutRequest(ctx: ChannelHandlerContext, msg: CachePutRequest) {
|
private fun handlePutRequest(ctx: ChannelHandlerContext, msg: CachePutRequest) {
|
||||||
val key = String(Base64.getUrlEncoder().encode(processCacheKey(msg.key, digestAlgorithm)))
|
val key = String(Base64.getUrlEncoder().encode(processCacheKey(msg.key, null, digestAlgorithm)))
|
||||||
val sink = cache.put(key, msg.metadata)
|
val sink = cache.put(key, msg.metadata)
|
||||||
inProgressPutRequest = InProgressPutRequest(msg.key, sink)
|
inProgressRequest = InProgressPutRequest(msg.key, sink)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCacheContent(ctx: ChannelHandlerContext, msg: CacheContent) {
|
private fun handleCacheContent(ctx: ChannelHandlerContext, msg: CacheContent) {
|
||||||
inProgressPutRequest!!.write(msg.content())
|
val request = inProgressRequest
|
||||||
|
if(request is InProgressPutRequest) {
|
||||||
|
request.write(msg.content())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLastCacheContent(ctx: ChannelHandlerContext, msg: LastCacheContent) {
|
private fun handleLastCacheContent(ctx: ChannelHandlerContext, msg: LastCacheContent) {
|
||||||
inProgressPutRequest?.let { request ->
|
when(val request = inProgressRequest) {
|
||||||
inProgressPutRequest = null
|
is InProgressPutRequest -> {
|
||||||
request.write(msg.content())
|
inProgressRequest = null
|
||||||
request.commit()
|
request.write(msg.content())
|
||||||
ctx.writeAndFlush(CachePutResponse(request.key))
|
request.commit()
|
||||||
|
sendMessageAndFlush(ctx, CachePutResponse(request.key))
|
||||||
|
}
|
||||||
|
is InProgressGetRequest -> {
|
||||||
|
val key = String(Base64.getUrlEncoder().encode(processCacheKey(request.request.key, null, digestAlgorithm)))
|
||||||
|
cache.get(key)?.also { entryValue ->
|
||||||
|
sendMessageAndFlush(ctx, CacheValueFoundResponse(request.request.key, entryValue.metadata))
|
||||||
|
entryValue.channel.let { channel ->
|
||||||
|
if(compressionEnabled) {
|
||||||
|
InflaterInputStream(Channels.newInputStream(channel)).use { stream ->
|
||||||
|
|
||||||
|
outerLoop@
|
||||||
|
while (true) {
|
||||||
|
val buf = ctx.alloc().heapBuffer(chunkSize)
|
||||||
|
while(buf.readableBytes() < chunkSize) {
|
||||||
|
val read = buf.writeBytes(stream, chunkSize)
|
||||||
|
if(read < 0) {
|
||||||
|
sendMessageAndFlush(ctx, LastCacheContent(buf))
|
||||||
|
break@outerLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendMessageAndFlush(ctx, CacheContent(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendMessage(ctx, ChunkedNioFile(channel, entryValue.offset, entryValue.size - entryValue.offset, chunkSize))
|
||||||
|
sendMessageAndFlush(ctx, LastHttpContent.EMPTY_LAST_CONTENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: sendMessageAndFlush(ctx, CacheValueNotFoundResponse(key))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
inProgressPutRequest?.rollback()
|
(inProgressRequest as? InProgressPutRequest)?.rollback()
|
||||||
super.exceptionCaught(ctx, cause)
|
super.exceptionCaught(ctx, cause)
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,14 @@
|
|||||||
package net.woggioni.rbcs.server.cache
|
package net.woggioni.rbcs.server.cache
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.zip.Deflater
|
||||||
import net.woggioni.rbcs.api.CacheProvider
|
import net.woggioni.rbcs.api.CacheProvider
|
||||||
import net.woggioni.rbcs.common.RBCS
|
import net.woggioni.rbcs.common.RBCS
|
||||||
import net.woggioni.rbcs.common.Xml
|
import net.woggioni.rbcs.common.Xml
|
||||||
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import java.nio.file.Path
|
|
||||||
import java.time.Duration
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
|
|
||||||
class FileSystemCacheProvider : CacheProvider<FileSystemCacheConfiguration> {
|
class FileSystemCacheProvider : CacheProvider<FileSystemCacheConfiguration> {
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,5 @@
|
|||||||
package net.woggioni.rbcs.server.cache
|
package net.woggioni.rbcs.server.cache
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
|
||||||
import net.woggioni.rbcs.api.AsyncCloseable
|
|
||||||
import net.woggioni.rbcs.api.CacheValueMetadata
|
|
||||||
import net.woggioni.rbcs.common.createLogger
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.PriorityQueue
|
import java.util.PriorityQueue
|
||||||
@@ -11,6 +7,9 @@ import java.util.concurrent.CompletableFuture
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
|
import net.woggioni.rbcs.api.AsyncCloseable
|
||||||
|
import net.woggioni.rbcs.api.CacheValueMetadata
|
||||||
|
import net.woggioni.rbcs.common.createLogger
|
||||||
|
|
||||||
private class CacheKey(private val value: ByteArray) {
|
private class CacheKey(private val value: ByteArray) {
|
||||||
override fun equals(other: Any?) = if (other is CacheKey) {
|
override fun equals(other: Any?) = if (other is CacheKey) {
|
||||||
@@ -22,7 +21,7 @@ private class CacheKey(private val value: ByteArray) {
|
|||||||
|
|
||||||
class CacheEntry(
|
class CacheEntry(
|
||||||
val metadata: CacheValueMetadata,
|
val metadata: CacheValueMetadata,
|
||||||
val content: ByteBuf
|
val content: ByteArray
|
||||||
)
|
)
|
||||||
|
|
||||||
class InMemoryCache(
|
class InMemoryCache(
|
||||||
@@ -66,8 +65,6 @@ class InMemoryCache(
|
|||||||
val removed = map.remove(el.key, value)
|
val removed = map.remove(el.key, value)
|
||||||
if (removed) {
|
if (removed) {
|
||||||
updateSizeAfterRemoval(value.content)
|
updateSizeAfterRemoval(value.content)
|
||||||
//Decrease the reference count for map
|
|
||||||
value.content.release()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
removalQueue.offer(el)
|
removalQueue.offer(el)
|
||||||
@@ -75,6 +72,7 @@ class InMemoryCache(
|
|||||||
cond.await(interval.toMillis(), TimeUnit.MILLISECONDS)
|
cond.await(interval.toMillis(), TimeUnit.MILLISECONDS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
map.clear()
|
||||||
}
|
}
|
||||||
complete(null)
|
complete(null)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
@@ -91,15 +89,13 @@ class InMemoryCache(
|
|||||||
val removed = map.remove(el.key, value)
|
val removed = map.remove(el.key, value)
|
||||||
if (removed) {
|
if (removed) {
|
||||||
val newSize = updateSizeAfterRemoval(value.content)
|
val newSize = updateSizeAfterRemoval(value.content)
|
||||||
//Decrease the reference count for map
|
|
||||||
value.content.release()
|
|
||||||
return newSize
|
return newSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSizeAfterRemoval(removed: ByteBuf): Long {
|
private fun updateSizeAfterRemoval(removed: ByteArray): Long {
|
||||||
mapSize -= removed.readableBytes()
|
mapSize -= removed.size
|
||||||
return mapSize
|
return mapSize
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +109,7 @@ class InMemoryCache(
|
|||||||
|
|
||||||
fun get(key: ByteArray) = lock.readLock().withLock {
|
fun get(key: ByteArray) = lock.readLock().withLock {
|
||||||
map[CacheKey(key)]?.run {
|
map[CacheKey(key)]?.run {
|
||||||
CacheEntry(metadata, content.retainedDuplicate())
|
CacheEntry(metadata, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,12 +119,8 @@ class InMemoryCache(
|
|||||||
) {
|
) {
|
||||||
val cacheKey = CacheKey(key)
|
val cacheKey = CacheKey(key)
|
||||||
lock.writeLock().withLock {
|
lock.writeLock().withLock {
|
||||||
val oldSize = map.put(cacheKey, value)?.let { old ->
|
val oldSize = map.put(cacheKey, value)?.content?.size ?: 0
|
||||||
val result = old.content.readableBytes()
|
val delta = value.content.size - oldSize
|
||||||
old.content.release()
|
|
||||||
result
|
|
||||||
} ?: 0
|
|
||||||
val delta = value.content.readableBytes() - oldSize
|
|
||||||
mapSize += delta
|
mapSize += delta
|
||||||
removalQueue.offer(RemovalQueueElement(cacheKey, value, Instant.now().plus(maxAge)))
|
removalQueue.offer(RemovalQueueElement(cacheKey, value, Instant.now().plus(maxAge)))
|
||||||
while (mapSize > maxSize) {
|
while (mapSize > maxSize) {
|
||||||
|
@@ -4,11 +4,10 @@ import io.netty.channel.ChannelFactory
|
|||||||
import io.netty.channel.EventLoopGroup
|
import io.netty.channel.EventLoopGroup
|
||||||
import io.netty.channel.socket.DatagramChannel
|
import io.netty.channel.socket.DatagramChannel
|
||||||
import io.netty.channel.socket.SocketChannel
|
import io.netty.channel.socket.SocketChannel
|
||||||
import io.netty.util.concurrent.Future
|
import java.time.Duration
|
||||||
import net.woggioni.rbcs.api.CacheHandlerFactory
|
import net.woggioni.rbcs.api.CacheHandlerFactory
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.common.RBCS
|
import net.woggioni.rbcs.common.RBCS
|
||||||
import java.time.Duration
|
|
||||||
|
|
||||||
data class InMemoryCacheConfiguration(
|
data class InMemoryCacheConfiguration(
|
||||||
val maxAge: Duration,
|
val maxAge: Duration,
|
||||||
@@ -16,7 +15,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 +22,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>
|
||||||
|
@@ -2,7 +2,12 @@ package net.woggioni.rbcs.server.cache
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.util.zip.Deflater
|
||||||
|
import java.util.zip.DeflaterOutputStream
|
||||||
|
import java.util.zip.InflaterOutputStream
|
||||||
|
import net.woggioni.rbcs.api.CacheHandler
|
||||||
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
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.CacheGetRequest
|
import net.woggioni.rbcs.api.message.CacheMessage.CacheGetRequest
|
||||||
@@ -13,18 +18,23 @@ import net.woggioni.rbcs.api.message.CacheMessage.CacheValueNotFoundResponse
|
|||||||
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
||||||
import net.woggioni.rbcs.common.ByteBufOutputStream
|
import net.woggioni.rbcs.common.ByteBufOutputStream
|
||||||
import net.woggioni.rbcs.common.RBCS.processCacheKey
|
import net.woggioni.rbcs.common.RBCS.processCacheKey
|
||||||
import java.util.zip.Deflater
|
|
||||||
import java.util.zip.DeflaterOutputStream
|
|
||||||
import java.util.zip.InflaterOutputStream
|
|
||||||
|
|
||||||
class InMemoryCacheHandler(
|
class InMemoryCacheHandler(
|
||||||
private val cache: InMemoryCache,
|
private val cache: InMemoryCache,
|
||||||
private val digestAlgorithm: String?,
|
private val digestAlgorithm: String?,
|
||||||
private val compressionEnabled: Boolean,
|
private val compressionEnabled: Boolean,
|
||||||
private val compressionLevel: Int
|
private val compressionLevel: Int
|
||||||
) : SimpleChannelInboundHandler<CacheMessage>() {
|
) : CacheHandler() {
|
||||||
|
|
||||||
private interface InProgressPutRequest : AutoCloseable {
|
private interface InProgressRequest : AutoCloseable {
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InProgressGetRequest(val request: CacheGetRequest) : InProgressRequest {
|
||||||
|
override fun close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface InProgressPutRequest : InProgressRequest {
|
||||||
val request: CachePutRequest
|
val request: CachePutRequest
|
||||||
val buf: ByteBuf
|
val buf: ByteBuf
|
||||||
|
|
||||||
@@ -33,18 +43,14 @@ class InMemoryCacheHandler(
|
|||||||
|
|
||||||
private inner class InProgressPlainPutRequest(ctx: ChannelHandlerContext, override val request: CachePutRequest) :
|
private inner class InProgressPlainPutRequest(ctx: ChannelHandlerContext, override val request: CachePutRequest) :
|
||||||
InProgressPutRequest {
|
InProgressPutRequest {
|
||||||
override val buf = ctx.alloc().compositeBuffer()
|
override val buf = ctx.alloc().compositeHeapBuffer()
|
||||||
|
|
||||||
private val stream = ByteBufOutputStream(buf).let {
|
|
||||||
if (compressionEnabled) {
|
|
||||||
DeflaterOutputStream(it, Deflater(compressionLevel))
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun append(buf: ByteBuf) {
|
override fun append(buf: ByteBuf) {
|
||||||
this.buf.addComponent(true, buf.retain())
|
if (buf.isDirect) {
|
||||||
|
this.buf.writeBytes(buf)
|
||||||
|
} else {
|
||||||
|
this.buf.addComponent(true, buf.retain())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
@@ -72,7 +78,7 @@ class InMemoryCacheHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var inProgressPutRequest: InProgressPutRequest? = null
|
private var inProgressRequest: InProgressRequest? = null
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: CacheMessage) {
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: CacheMessage) {
|
||||||
when (msg) {
|
when (msg) {
|
||||||
@@ -85,24 +91,11 @@ class InMemoryCacheHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGetRequest(ctx: ChannelHandlerContext, msg: CacheGetRequest) {
|
private fun handleGetRequest(ctx: ChannelHandlerContext, msg: CacheGetRequest) {
|
||||||
cache.get(processCacheKey(msg.key, digestAlgorithm))?.let { value ->
|
inProgressRequest = InProgressGetRequest(msg)
|
||||||
ctx.writeAndFlush(CacheValueFoundResponse(msg.key, value.metadata))
|
|
||||||
if (compressionEnabled) {
|
|
||||||
val buf = ctx.alloc().heapBuffer()
|
|
||||||
InflaterOutputStream(ByteBufOutputStream(buf)).use {
|
|
||||||
value.content.readBytes(it, value.content.readableBytes())
|
|
||||||
value.content.release()
|
|
||||||
buf.retain()
|
|
||||||
}
|
|
||||||
ctx.writeAndFlush(LastCacheContent(buf))
|
|
||||||
} else {
|
|
||||||
ctx.writeAndFlush(LastCacheContent(value.content))
|
|
||||||
}
|
|
||||||
} ?: ctx.writeAndFlush(CacheValueNotFoundResponse())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePutRequest(ctx: ChannelHandlerContext, msg: CachePutRequest) {
|
private fun handlePutRequest(ctx: ChannelHandlerContext, msg: CachePutRequest) {
|
||||||
inProgressPutRequest = if(compressionEnabled) {
|
inProgressRequest = if (compressionEnabled) {
|
||||||
InProgressCompressedPutRequest(ctx, msg)
|
InProgressCompressedPutRequest(ctx, msg)
|
||||||
} else {
|
} else {
|
||||||
InProgressPlainPutRequest(ctx, msg)
|
InProgressPlainPutRequest(ctx, msg)
|
||||||
@@ -110,27 +103,55 @@ class InMemoryCacheHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCacheContent(ctx: ChannelHandlerContext, msg: CacheContent) {
|
private fun handleCacheContent(ctx: ChannelHandlerContext, msg: CacheContent) {
|
||||||
inProgressPutRequest?.append(msg.content())
|
val req = inProgressRequest
|
||||||
|
if (req is InProgressPutRequest) {
|
||||||
|
req.append(msg.content())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLastCacheContent(ctx: ChannelHandlerContext, msg: LastCacheContent) {
|
private fun handleLastCacheContent(ctx: ChannelHandlerContext, msg: LastCacheContent) {
|
||||||
handleCacheContent(ctx, msg)
|
handleCacheContent(ctx, msg)
|
||||||
inProgressPutRequest?.let { inProgressRequest ->
|
when (val req = inProgressRequest) {
|
||||||
inProgressPutRequest = null
|
is InProgressGetRequest -> {
|
||||||
val buf = inProgressRequest.buf
|
// this.inProgressRequest = null
|
||||||
buf.retain()
|
cache.get(processCacheKey(req.request.key, null, digestAlgorithm))?.let { value ->
|
||||||
inProgressRequest.close()
|
sendMessageAndFlush(ctx, CacheValueFoundResponse(req.request.key, value.metadata))
|
||||||
val cacheKey = processCacheKey(inProgressRequest.request.key, digestAlgorithm)
|
if (compressionEnabled) {
|
||||||
cache.put(cacheKey, CacheEntry(inProgressRequest.request.metadata, buf))
|
val buf = ctx.alloc().heapBuffer()
|
||||||
ctx.writeAndFlush(CachePutResponse(inProgressRequest.request.key))
|
InflaterOutputStream(ByteBufOutputStream(buf)).use {
|
||||||
|
it.write(value.content)
|
||||||
|
buf.retain()
|
||||||
|
}
|
||||||
|
sendMessage(ctx, LastCacheContent(buf))
|
||||||
|
} else {
|
||||||
|
val buf = ctx.alloc().heapBuffer()
|
||||||
|
ByteBufOutputStream(buf).use {
|
||||||
|
it.write(value.content)
|
||||||
|
buf.retain()
|
||||||
|
}
|
||||||
|
sendMessage(ctx, LastCacheContent(buf))
|
||||||
|
}
|
||||||
|
} ?: sendMessage(ctx, CacheValueNotFoundResponse(req.request.key))
|
||||||
|
}
|
||||||
|
|
||||||
|
is InProgressPutRequest -> {
|
||||||
|
this.inProgressRequest = null
|
||||||
|
val buf = req.buf
|
||||||
|
buf.retain()
|
||||||
|
req.close()
|
||||||
|
|
||||||
|
val bytes = ByteArray(buf.readableBytes()).also(buf::readBytes)
|
||||||
|
buf.release()
|
||||||
|
val cacheKey = processCacheKey(req.request.key, null, digestAlgorithm)
|
||||||
|
cache.put(cacheKey, CacheEntry(req.request.metadata, bytes))
|
||||||
|
sendMessageAndFlush(ctx, CachePutResponse(req.request.key))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
inProgressPutRequest?.let { req ->
|
inProgressRequest?.close()
|
||||||
req.buf.release()
|
inProgressRequest = null
|
||||||
inProgressPutRequest = null
|
|
||||||
}
|
|
||||||
super.exceptionCaught(ctx, cause)
|
super.exceptionCaught(ctx, cause)
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,13 +1,13 @@
|
|||||||
package net.woggioni.rbcs.server.cache
|
package net.woggioni.rbcs.server.cache
|
||||||
|
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.zip.Deflater
|
||||||
import net.woggioni.rbcs.api.CacheProvider
|
import net.woggioni.rbcs.api.CacheProvider
|
||||||
import net.woggioni.rbcs.common.RBCS
|
import net.woggioni.rbcs.common.RBCS
|
||||||
import net.woggioni.rbcs.common.Xml
|
import net.woggioni.rbcs.common.Xml
|
||||||
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import java.time.Duration
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
|
|
||||||
class InMemoryCacheProvider : CacheProvider<InMemoryCacheConfiguration> {
|
class InMemoryCacheProvider : CacheProvider<InMemoryCacheConfiguration> {
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package net.woggioni.rbcs.server.configuration
|
package net.woggioni.rbcs.server.configuration
|
||||||
|
|
||||||
|
import java.util.ServiceLoader
|
||||||
import net.woggioni.rbcs.api.CacheProvider
|
import net.woggioni.rbcs.api.CacheProvider
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import java.util.ServiceLoader
|
|
||||||
|
|
||||||
object CacheSerializers {
|
object CacheSerializers {
|
||||||
val index = (Configuration::class.java.module.layer?.let { layer ->
|
val index = (Configuration::class.java.module.layer?.let { layer ->
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
package net.woggioni.rbcs.server.configuration
|
package net.woggioni.rbcs.server.configuration
|
||||||
|
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.api.Configuration.Authentication
|
import net.woggioni.rbcs.api.Configuration.Authentication
|
||||||
import net.woggioni.rbcs.api.Configuration.BasicAuthentication
|
import net.woggioni.rbcs.api.Configuration.BasicAuthentication
|
||||||
@@ -18,20 +21,19 @@ import net.woggioni.rbcs.common.Xml.Companion.renderAttribute
|
|||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.TypeInfo
|
import org.w3c.dom.TypeInfo
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
|
|
||||||
object Parser {
|
object Parser {
|
||||||
fun parse(document: Document): Configuration {
|
fun parse(document: Document): Configuration {
|
||||||
val root = document.documentElement
|
val root = document.documentElement
|
||||||
val anonymousUser = User("", null, emptySet(), 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 rateLimiter = Configuration.RateLimiter(false, 0x100000, 100)
|
||||||
var eventExecutor: Configuration.EventExecutor = Configuration.EventExecutor(true)
|
var eventExecutor: Configuration.EventExecutor = Configuration.EventExecutor(true)
|
||||||
var cache: Cache? = null
|
var cache: Cache? = null
|
||||||
var host = "127.0.0.1"
|
var host = "127.0.0.1"
|
||||||
@@ -119,20 +121,36 @@ 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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"event-executor" -> {
|
"event-executor" -> {
|
||||||
val useVirtualThread = root.renderAttribute("use-virtual-threads")
|
val useVirtualThread = child.renderAttribute("use-virtual-threads")
|
||||||
?.let(String::toBoolean) ?: true
|
?.let(String::toBoolean) ?: true
|
||||||
eventExecutor = Configuration.EventExecutor(useVirtualThread)
|
eventExecutor = Configuration.EventExecutor(useVirtualThread)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"rate-limiter" -> {
|
||||||
|
val delayResponse = child.renderAttribute("delay-response")
|
||||||
|
?.let(String::toBoolean)
|
||||||
|
?: false
|
||||||
|
val messageBufferSize = child.renderAttribute("message-buffer-size")
|
||||||
|
?.let(Integer::decode)
|
||||||
|
?: 0x100000
|
||||||
|
val maxQueuedMessages = child.renderAttribute("max-queued-messages")
|
||||||
|
?.let(Integer::decode)
|
||||||
|
?: 100
|
||||||
|
rateLimiter = Configuration.RateLimiter(delayResponse, messageBufferSize, maxQueuedMessages)
|
||||||
|
}
|
||||||
|
|
||||||
"tls" -> {
|
"tls" -> {
|
||||||
var keyStore: KeyStore? = null
|
var keyStore: KeyStore? = null
|
||||||
var trustStore: TrustStore? = null
|
var trustStore: TrustStore? = null
|
||||||
@@ -180,6 +198,7 @@ object Parser {
|
|||||||
incomingConnectionsBacklogSize,
|
incomingConnectionsBacklogSize,
|
||||||
serverPath,
|
serverPath,
|
||||||
eventExecutor,
|
eventExecutor,
|
||||||
|
rateLimiter,
|
||||||
connection,
|
connection,
|
||||||
users,
|
users,
|
||||||
groups,
|
groups,
|
||||||
|
@@ -40,11 +40,17 @@ 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") {
|
||||||
attr("use-virtual-threads", conf.eventExecutor.isUseVirtualThreads.toString())
|
attr("use-virtual-threads", conf.eventExecutor.isUseVirtualThreads.toString())
|
||||||
}
|
}
|
||||||
|
node("rate-limiter") {
|
||||||
|
attr("delay-response", conf.rateLimiter.isDelayRequest.toString())
|
||||||
|
attr("max-queued-messages", conf.rateLimiter.maxQueuedMessages.toString())
|
||||||
|
attr("message-buffer-size", conf.rateLimiter.messageBufferSize.toString())
|
||||||
|
}
|
||||||
val cache = conf.cache
|
val cache = conf.cache
|
||||||
val serializer : CacheProvider<Configuration.Cache> =
|
val serializer : CacheProvider<Configuration.Cache> =
|
||||||
(CacheSerializers.index[cache.namespaceURI to cache.typeName] as? CacheProvider<Configuration.Cache>) ?: throw NotImplementedError()
|
(CacheSerializers.index[cache.namespaceURI to cache.typeName] as? CacheProvider<Configuration.Cache>) ?: throw NotImplementedError()
|
||||||
|
@@ -13,6 +13,10 @@ import io.netty.handler.codec.http.HttpResponseStatus
|
|||||||
import io.netty.handler.codec.http.HttpVersion
|
import io.netty.handler.codec.http.HttpVersion
|
||||||
import io.netty.handler.timeout.ReadTimeoutException
|
import io.netty.handler.timeout.ReadTimeoutException
|
||||||
import io.netty.handler.timeout.WriteTimeoutException
|
import io.netty.handler.timeout.WriteTimeoutException
|
||||||
|
import java.net.ConnectException
|
||||||
|
import java.net.SocketException
|
||||||
|
import javax.net.ssl.SSLException
|
||||||
|
import javax.net.ssl.SSLPeerUnverifiedException
|
||||||
import net.woggioni.rbcs.api.exception.CacheException
|
import net.woggioni.rbcs.api.exception.CacheException
|
||||||
import net.woggioni.rbcs.api.exception.ContentTooLargeException
|
import net.woggioni.rbcs.api.exception.ContentTooLargeException
|
||||||
import net.woggioni.rbcs.common.contextLogger
|
import net.woggioni.rbcs.common.contextLogger
|
||||||
@@ -20,13 +24,12 @@ import net.woggioni.rbcs.common.debug
|
|||||||
import net.woggioni.rbcs.common.log
|
import net.woggioni.rbcs.common.log
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
import org.slf4j.spi.LoggingEventBuilder
|
import org.slf4j.spi.LoggingEventBuilder
|
||||||
import java.net.ConnectException
|
|
||||||
import java.net.SocketException
|
|
||||||
import javax.net.ssl.SSLException
|
|
||||||
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(
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
|
import io.netty.handler.codec.http.HttpContent
|
||||||
|
|
||||||
|
class BlackHoleRequestHandler : SimpleChannelInboundHandler<HttpContent>() {
|
||||||
|
companion object {
|
||||||
|
val NAME = BlackHoleRequestHandler::class.java.name
|
||||||
|
}
|
||||||
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpContent) {
|
||||||
|
}
|
||||||
|
}
|
@@ -1,28 +0,0 @@
|
|||||||
package net.woggioni.rbcs.server.handler
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandler.Sharable
|
|
||||||
import io.netty.channel.ChannelHandlerContext
|
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
|
||||||
import io.netty.handler.codec.http.HttpContent
|
|
||||||
import io.netty.handler.codec.http.LastHttpContent
|
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.CacheContent
|
|
||||||
import net.woggioni.rbcs.api.message.CacheMessage.LastCacheContent
|
|
||||||
|
|
||||||
@Sharable
|
|
||||||
object CacheContentHandler : SimpleChannelInboundHandler<HttpContent>() {
|
|
||||||
val NAME = this::class.java.name
|
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpContent) {
|
|
||||||
when(msg) {
|
|
||||||
is LastHttpContent -> {
|
|
||||||
ctx.fireChannelRead(LastCacheContent(msg.content().retain()))
|
|
||||||
ctx.pipeline().remove(this)
|
|
||||||
}
|
|
||||||
else -> ctx.fireChannelRead(CacheContent(msg.content().retain()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) {
|
|
||||||
super.exceptionCaught(ctx, cause)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,65 @@
|
|||||||
|
package net.woggioni.rbcs.server.handler
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufHolder
|
||||||
|
import io.netty.channel.ChannelDuplexHandler
|
||||||
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.ChannelPromise
|
||||||
|
import io.netty.handler.codec.http.LastHttpContent
|
||||||
|
import net.woggioni.rbcs.common.createLogger
|
||||||
|
|
||||||
|
class ReadTriggerDuplexHandler : ChannelDuplexHandler() {
|
||||||
|
companion object {
|
||||||
|
val NAME = ReadTriggerDuplexHandler::class.java.name
|
||||||
|
private val log = createLogger<ReadTriggerDuplexHandler>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var inFlight = 0
|
||||||
|
private val messageBuffer = ArrayDeque<Any>()
|
||||||
|
|
||||||
|
override fun handlerAdded(ctx: ChannelHandlerContext) {
|
||||||
|
ctx.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
||||||
|
if(inFlight > 0) {
|
||||||
|
messageBuffer.addLast(msg)
|
||||||
|
} else {
|
||||||
|
super.channelRead(ctx, msg)
|
||||||
|
if(msg !is LastHttpContent) {
|
||||||
|
invokeRead(ctx)
|
||||||
|
} else {
|
||||||
|
inFlight += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invokeRead(ctx : ChannelHandlerContext) {
|
||||||
|
if(messageBuffer.isEmpty()) {
|
||||||
|
ctx.read()
|
||||||
|
} else {
|
||||||
|
this.channelRead(ctx, messageBuffer.removeFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(
|
||||||
|
ctx: ChannelHandlerContext,
|
||||||
|
msg: Any,
|
||||||
|
promise: ChannelPromise
|
||||||
|
) {
|
||||||
|
super.write(ctx, msg, promise)
|
||||||
|
if(msg is LastHttpContent) {
|
||||||
|
inFlight -= 1
|
||||||
|
invokeRead(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun channelInactive(ctx: ChannelHandlerContext) {
|
||||||
|
while(messageBuffer.isNotEmpty()) {
|
||||||
|
val msg = messageBuffer.removeFirst()
|
||||||
|
if(msg is ByteBufHolder) {
|
||||||
|
msg.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.channelInactive(ctx)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,14 @@
|
|||||||
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
|
||||||
import io.netty.handler.codec.http.DefaultHttpContent
|
import io.netty.handler.codec.http.DefaultHttpContent
|
||||||
import io.netty.handler.codec.http.DefaultHttpResponse
|
import io.netty.handler.codec.http.DefaultHttpResponse
|
||||||
import io.netty.handler.codec.http.DefaultLastHttpContent
|
import io.netty.handler.codec.http.DefaultLastHttpContent
|
||||||
|
import io.netty.handler.codec.http.HttpContent
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames
|
import io.netty.handler.codec.http.HttpHeaderNames
|
||||||
import io.netty.handler.codec.http.HttpHeaderValues
|
import io.netty.handler.codec.http.HttpHeaderValues
|
||||||
import io.netty.handler.codec.http.HttpHeaders
|
import io.netty.handler.codec.http.HttpHeaders
|
||||||
@@ -15,6 +17,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 java.nio.file.Path
|
||||||
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,15 +31,14 @@ 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 java.nio.file.Path
|
import net.woggioni.rbcs.server.exception.ExceptionHandler
|
||||||
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
|
||||||
@@ -59,20 +62,36 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var cacheRequestInProgress : Boolean = false
|
||||||
|
|
||||||
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
||||||
when (msg) {
|
when (msg) {
|
||||||
is HttpRequest -> handleRequest(ctx, msg)
|
is HttpRequest -> handleRequest(ctx, msg)
|
||||||
|
is HttpContent -> {
|
||||||
|
if(cacheRequestInProgress) {
|
||||||
|
if(msg is LastHttpContent) {
|
||||||
|
super.channelRead(ctx, LastCacheContent(msg.content().retain()))
|
||||||
|
cacheRequestInProgress = false
|
||||||
|
} else {
|
||||||
|
super.channelRead(ctx, CacheContent(msg.content().retain()))
|
||||||
|
}
|
||||||
|
msg.release()
|
||||||
|
} else {
|
||||||
|
super.channelRead(ctx, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> super.channelRead(ctx, msg)
|
else -> super.channelRead(ctx, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise?) {
|
override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise?) {
|
||||||
if (msg is CacheMessage) {
|
if (msg is CacheMessage) {
|
||||||
try {
|
try {
|
||||||
when (msg) {
|
when (msg) {
|
||||||
is CachePutResponse -> {
|
is CachePutResponse -> {
|
||||||
|
log.debug(ctx) {
|
||||||
|
"Added value for key '${msg.key}' to build cache"
|
||||||
|
}
|
||||||
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 +107,9 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
is CacheValueNotFoundResponse -> {
|
is CacheValueNotFoundResponse -> {
|
||||||
|
log.debug(ctx) {
|
||||||
|
"Value not found for key '${msg.key}'"
|
||||||
|
}
|
||||||
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())
|
||||||
@@ -95,6 +117,9 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
is CacheValueFoundResponse -> {
|
is CacheValueFoundResponse -> {
|
||||||
|
log.debug(ctx) {
|
||||||
|
"Retrieved value for key '${msg.key}'"
|
||||||
|
}
|
||||||
val response = DefaultHttpResponse(httpVersion, HttpResponseStatus.OK)
|
val response = DefaultHttpResponse(httpVersion, HttpResponseStatus.OK)
|
||||||
response.headers().apply {
|
response.headers().apply {
|
||||||
set(HttpHeaderNames.CONTENT_TYPE, msg.metadata.mimeType ?: HttpHeaderValues.APPLICATION_OCTET_STREAM)
|
set(HttpHeaderNames.CONTENT_TYPE, msg.metadata.mimeType ?: HttpHeaderValues.APPLICATION_OCTET_STREAM)
|
||||||
@@ -127,6 +152,8 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
} finally {
|
} finally {
|
||||||
resetRequestMetadata()
|
resetRequestMetadata()
|
||||||
}
|
}
|
||||||
|
} else if(msg is LastHttpContent) {
|
||||||
|
ctx.write(msg, promise)
|
||||||
} else super.write(ctx, msg, promise)
|
} else super.write(ctx, msg, promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,13 +164,16 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
if (method === HttpMethod.GET) {
|
if (method === HttpMethod.GET) {
|
||||||
val path = Path.of(msg.uri()).normalize()
|
val path = Path.of(msg.uri()).normalize()
|
||||||
if (path.startsWith(serverPrefix)) {
|
if (path.startsWith(serverPrefix)) {
|
||||||
|
cacheRequestInProgress = true
|
||||||
val relativePath = serverPrefix.relativize(path)
|
val relativePath = serverPrefix.relativize(path)
|
||||||
val key = relativePath.toString()
|
val key : String = relativePath.toString()
|
||||||
ctx.pipeline().addAfter(NAME, CacheContentHandler.NAME, CacheContentHandler)
|
val cacheHandler = cacheHandlerSupplier()
|
||||||
|
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, cacheHandler)
|
||||||
key.let(::CacheGetRequest)
|
key.let(::CacheGetRequest)
|
||||||
.let(ctx::fireChannelRead)
|
.let(ctx::fireChannelRead)
|
||||||
?: ctx.channel().write(CacheValueNotFoundResponse())
|
?: ctx.channel().write(CacheValueNotFoundResponse(key))
|
||||||
} else {
|
} else {
|
||||||
|
cacheRequestInProgress = false
|
||||||
log.warn(ctx) {
|
log.warn(ctx) {
|
||||||
"Got request for unhandled path '${msg.uri()}'"
|
"Got request for unhandled path '${msg.uri()}'"
|
||||||
}
|
}
|
||||||
@@ -154,20 +184,21 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
} else if (method === HttpMethod.PUT) {
|
} else if (method === HttpMethod.PUT) {
|
||||||
val path = Path.of(msg.uri()).normalize()
|
val path = Path.of(msg.uri()).normalize()
|
||||||
if (path.startsWith(serverPrefix)) {
|
if (path.startsWith(serverPrefix)) {
|
||||||
|
cacheRequestInProgress = true
|
||||||
val relativePath = serverPrefix.relativize(path)
|
val relativePath = serverPrefix.relativize(path)
|
||||||
val key = relativePath.toString()
|
val key = relativePath.toString()
|
||||||
log.debug(ctx) {
|
val cacheHandler = cacheHandlerSupplier()
|
||||||
"Added value for key '$key' to build cache"
|
ctx.pipeline().addAfter(NAME, null, cacheHandler)
|
||||||
}
|
|
||||||
ctx.pipeline().addAfter(NAME, CacheContentHandler.NAME, CacheContentHandler)
|
|
||||||
path.fileName?.toString()
|
path.fileName?.toString()
|
||||||
?.let {
|
?.let {
|
||||||
val mimeType = HttpUtil.getMimeType(msg)?.toString()
|
val mimeType = HttpUtil.getMimeType(msg)?.toString()
|
||||||
CachePutRequest(key, CacheValueMetadata(msg.headers().get(HttpHeaderNames.CONTENT_DISPOSITION), mimeType))
|
CachePutRequest(key, CacheValueMetadata(msg.headers().get(HttpHeaderNames.CONTENT_DISPOSITION), mimeType))
|
||||||
}
|
}
|
||||||
?.let(ctx::fireChannelRead)
|
?.let(ctx::fireChannelRead)
|
||||||
?: ctx.channel().write(CacheValueNotFoundResponse())
|
?: ctx.channel().write(CacheValueNotFoundResponse(key))
|
||||||
} else {
|
} else {
|
||||||
|
cacheRequestInProgress = false
|
||||||
log.warn(ctx) {
|
log.warn(ctx) {
|
||||||
"Got request for unhandled path '${msg.uri()}'"
|
"Got request for unhandled path '${msg.uri()}'"
|
||||||
}
|
}
|
||||||
@@ -176,8 +207,11 @@ class ServerHandler(private val serverPrefix: Path) :
|
|||||||
ctx.writeAndFlush(response)
|
ctx.writeAndFlush(response)
|
||||||
}
|
}
|
||||||
} else if (method == HttpMethod.TRACE) {
|
} else if (method == HttpMethod.TRACE) {
|
||||||
|
cacheRequestInProgress = false
|
||||||
|
ctx.pipeline().addAfter(NAME, null, TraceHandler)
|
||||||
super.channelRead(ctx, msg)
|
super.channelRead(ctx, msg)
|
||||||
} else {
|
} else {
|
||||||
|
cacheRequestInProgress = false
|
||||||
log.warn(ctx) {
|
log.warn(ctx) {
|
||||||
"Got request with unhandled method '${msg.method().name()}'"
|
"Got request with unhandled method '${msg.method().name()}'"
|
||||||
}
|
}
|
||||||
@@ -187,43 +221,7 @@ 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)
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
package net.woggioni.rbcs.server.throttling
|
package net.woggioni.rbcs.server.throttling
|
||||||
|
|
||||||
import net.woggioni.jwo.Bucket
|
|
||||||
import net.woggioni.rbcs.api.Configuration
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.util.Arrays
|
import java.util.Arrays
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
import net.woggioni.jwo.Bucket
|
||||||
|
import net.woggioni.rbcs.api.Configuration
|
||||||
|
|
||||||
class BucketManager private constructor(
|
class BucketManager private constructor(
|
||||||
private val bucketsByUser: Map<Configuration.User, List<Bucket>> = HashMap(),
|
private val bucketsByUser: Map<Configuration.User, List<Bucket>> = HashMap(),
|
||||||
|
@@ -1,32 +1,50 @@
|
|||||||
package net.woggioni.rbcs.server.throttling
|
package net.woggioni.rbcs.server.throttling
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufHolder
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter
|
import io.netty.channel.ChannelInboundHandlerAdapter
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
||||||
import io.netty.handler.codec.http.HttpContent
|
import io.netty.handler.codec.http.FullHttpMessage
|
||||||
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.HttpResponseStatus
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
import io.netty.handler.codec.http.HttpVersion
|
import io.netty.handler.codec.http.HttpVersion
|
||||||
|
import io.netty.handler.codec.http.LastHttpContent
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.ArrayDeque
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import net.woggioni.jwo.Bucket
|
import net.woggioni.jwo.Bucket
|
||||||
import net.woggioni.jwo.LongMath
|
import net.woggioni.jwo.LongMath
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.common.createLogger
|
import net.woggioni.rbcs.common.createLogger
|
||||||
|
import net.woggioni.rbcs.common.debug
|
||||||
import net.woggioni.rbcs.server.RemoteBuildCacheServer
|
import net.woggioni.rbcs.server.RemoteBuildCacheServer
|
||||||
import java.net.InetSocketAddress
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
class ThrottlingHandler(
|
||||||
class ThrottlingHandler(private val bucketManager : BucketManager,
|
private val bucketManager: BucketManager,
|
||||||
private val connectionConfiguration : Configuration.Connection) : ChannelInboundHandlerAdapter() {
|
rateLimiterConfiguration: Configuration.RateLimiter,
|
||||||
|
connectionConfiguration: Configuration.Connection
|
||||||
|
) : ChannelInboundHandlerAdapter() {
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private val log = createLogger<ThrottlingHandler>()
|
private val log = createLogger<ThrottlingHandler>()
|
||||||
|
|
||||||
|
fun nextAttemptIsWithinThreshold(nextAttemptNanos : Long, waitThreshold : Duration) : Boolean {
|
||||||
|
val waitDuration = Duration.of(LongMath.ceilDiv(nextAttemptNanos, 100_000_000L) * 100L, ChronoUnit.MILLIS)
|
||||||
|
return waitDuration < waitThreshold
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var queuedContent : MutableList<HttpContent>? = null
|
private class RefusedRequest
|
||||||
|
|
||||||
|
private val maxMessageBufferSize = rateLimiterConfiguration.messageBufferSize
|
||||||
|
private val maxQueuedMessages = rateLimiterConfiguration.maxQueuedMessages
|
||||||
|
private val delayRequests = rateLimiterConfiguration.isDelayRequest
|
||||||
|
private var requestBufferSize : Int = 0
|
||||||
|
private var valveClosed = false
|
||||||
|
private var queuedContent = ArrayDeque<Any>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the suggested waiting time from the bucket is lower than this
|
* If the suggested waiting time from the bucket is lower than this
|
||||||
@@ -39,38 +57,149 @@ class ThrottlingHandler(private val bucketManager : BucketManager,
|
|||||||
connectionConfiguration.writeIdleTimeout
|
connectionConfiguration.writeIdleTimeout
|
||||||
).dividedBy(2)
|
).dividedBy(2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
||||||
if(msg is HttpRequest) {
|
if(valveClosed) {
|
||||||
val buckets = mutableListOf<Bucket>()
|
if(msg !is HttpRequest && msg is ByteBufHolder) {
|
||||||
val user = ctx.channel().attr(RemoteBuildCacheServer.userAttribute).get()
|
val newBufferSize = requestBufferSize + msg.content().readableBytes()
|
||||||
if (user != null) {
|
if(newBufferSize > maxMessageBufferSize || queuedContent.size + 1 > maxQueuedMessages) {
|
||||||
bucketManager.getBucketByUser(user)?.let(buckets::addAll)
|
log.debug {
|
||||||
|
if (newBufferSize > maxMessageBufferSize) {
|
||||||
|
"New message part exceeds maxMessageBufferSize, removing previous chunks"
|
||||||
|
} else {
|
||||||
|
"New message part exceeds maxQueuedMessages, removing previous chunks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If this message overflows the maxMessageBufferSize,
|
||||||
|
// then remove the previously enqueued chunks of the request from the deque,
|
||||||
|
// then discard the message
|
||||||
|
while(true) {
|
||||||
|
val tail = queuedContent.last()
|
||||||
|
if(tail is ByteBufHolder) {
|
||||||
|
requestBufferSize -= tail.content().readableBytes()
|
||||||
|
tail.release()
|
||||||
|
}
|
||||||
|
queuedContent.removeLast()
|
||||||
|
if(tail is HttpRequest) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg.release()
|
||||||
|
//Add a placeholder to remember to return a 429 response corresponding to this request
|
||||||
|
queuedContent.addLast(RefusedRequest())
|
||||||
|
} else {
|
||||||
|
//If the message does not overflow maxMessageBufferSize, just add it to the deque
|
||||||
|
queuedContent.addLast(msg)
|
||||||
|
requestBufferSize = newBufferSize
|
||||||
|
}
|
||||||
|
} else if(msg is HttpRequest && msg is FullHttpMessage){
|
||||||
|
val newBufferSize = requestBufferSize + msg.content().readableBytes()
|
||||||
|
|
||||||
|
// If this message overflows the maxMessageBufferSize,
|
||||||
|
// discard the message
|
||||||
|
if(newBufferSize > maxMessageBufferSize || queuedContent.size + 1 > maxQueuedMessages) {
|
||||||
|
log.debug {
|
||||||
|
if (newBufferSize > maxMessageBufferSize) {
|
||||||
|
"New message exceeds maxMessageBufferSize, discarding it"
|
||||||
|
} else {
|
||||||
|
"New message exceeds maxQueuedMessages, discarding it"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg.release()
|
||||||
|
//Add a placeholder to remember to return a 429 response corresponding to this request
|
||||||
|
queuedContent.addLast(RefusedRequest())
|
||||||
|
} else {
|
||||||
|
//If the message does not exceed maxMessageBufferSize or maxQueuedMessages, just add it to the deque
|
||||||
|
queuedContent.addLast(msg)
|
||||||
|
requestBufferSize = newBufferSize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queuedContent.addLast(msg)
|
||||||
}
|
}
|
||||||
val groups = ctx.channel().attr(RemoteBuildCacheServer.groupAttribute).get() ?: emptySet()
|
} else {
|
||||||
if (groups.isNotEmpty()) {
|
entryPoint(ctx, msg)
|
||||||
groups.forEach { group ->
|
}
|
||||||
bucketManager.getBucketByGroup(group)?.let(buckets::add)
|
}
|
||||||
|
|
||||||
|
private fun entryPoint(ctx : ChannelHandlerContext, msg : Any) {
|
||||||
|
if(msg is RefusedRequest) {
|
||||||
|
sendThrottledResponse(ctx, null)
|
||||||
|
if(queuedContent.isEmpty()) {
|
||||||
|
valveClosed = false
|
||||||
|
} else {
|
||||||
|
val head = queuedContent.poll()
|
||||||
|
if(head is ByteBufHolder) {
|
||||||
|
requestBufferSize -= head.content().readableBytes()
|
||||||
|
}
|
||||||
|
entryPoint(ctx, head)
|
||||||
|
}
|
||||||
|
} else if(msg is HttpRequest) {
|
||||||
|
val nextAttempt = getNextAttempt(ctx)
|
||||||
|
if (nextAttempt < 0) {
|
||||||
|
super.channelRead(ctx, msg)
|
||||||
|
if(msg !is LastHttpContent) {
|
||||||
|
while (true) {
|
||||||
|
val head = queuedContent.poll() ?: break
|
||||||
|
if(head is ByteBufHolder) {
|
||||||
|
requestBufferSize -= head.content().readableBytes()
|
||||||
|
}
|
||||||
|
super.channelRead(ctx, head)
|
||||||
|
if (head is LastHttpContent) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(queuedContent.isEmpty()) {
|
||||||
|
valveClosed = false
|
||||||
|
} else {
|
||||||
|
val head = queuedContent.poll()
|
||||||
|
if(head is ByteBufHolder) {
|
||||||
|
requestBufferSize -= head.content().readableBytes()
|
||||||
|
}
|
||||||
|
entryPoint(ctx, head)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val waitDuration = Duration.of(LongMath.ceilDiv(nextAttempt, 100_000_000L) * 100L, ChronoUnit.MILLIS)
|
||||||
|
if (delayRequests && nextAttemptIsWithinThreshold(nextAttempt, waitThreshold)) {
|
||||||
|
valveClosed = true
|
||||||
|
ctx.executor().schedule({
|
||||||
|
entryPoint(ctx, msg)
|
||||||
|
}, waitDuration.toMillis(), TimeUnit.MILLISECONDS)
|
||||||
|
} else {
|
||||||
|
sendThrottledResponse(ctx, waitDuration)
|
||||||
|
if(queuedContent.isEmpty()) {
|
||||||
|
valveClosed = false
|
||||||
|
} else {
|
||||||
|
val head = queuedContent.poll()
|
||||||
|
if(head is ByteBufHolder) {
|
||||||
|
requestBufferSize -= head.content().readableBytes()
|
||||||
|
}
|
||||||
|
entryPoint(ctx, head)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user == null && groups.isEmpty()) {
|
|
||||||
bucketManager.getBucketByAddress(ctx.channel().remoteAddress() as InetSocketAddress)?.let(buckets::add)
|
|
||||||
}
|
|
||||||
if (buckets.isEmpty()) {
|
|
||||||
super.channelRead(ctx, msg)
|
|
||||||
} else {
|
|
||||||
handleBuckets(buckets, ctx, msg, true)
|
|
||||||
}
|
|
||||||
ctx.channel().id()
|
|
||||||
} else if(msg is HttpContent) {
|
|
||||||
queuedContent?.add(msg) ?: super.channelRead(ctx, msg)
|
|
||||||
} else {
|
} else {
|
||||||
super.channelRead(ctx, msg)
|
super.channelRead(ctx, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBuckets(buckets: List<Bucket>, ctx: ChannelHandlerContext, msg: Any, delayResponse: Boolean) {
|
/**
|
||||||
|
* Returns the number amount of milliseconds to wait before the requests can be processed
|
||||||
|
* or -1 if the request can be performed immediately
|
||||||
|
*/
|
||||||
|
private fun getNextAttempt(ctx : ChannelHandlerContext) : Long {
|
||||||
|
val buckets = mutableListOf<Bucket>()
|
||||||
|
val user = ctx.channel().attr(RemoteBuildCacheServer.userAttribute).get()
|
||||||
|
if (user != null) {
|
||||||
|
bucketManager.getBucketByUser(user)?.let(buckets::addAll)
|
||||||
|
}
|
||||||
|
val groups = ctx.channel().attr(RemoteBuildCacheServer.groupAttribute).get() ?: emptySet()
|
||||||
|
if (groups.isNotEmpty()) {
|
||||||
|
groups.forEach { group ->
|
||||||
|
bucketManager.getBucketByGroup(group)?.let(buckets::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (user == null && groups.isEmpty()) {
|
||||||
|
bucketManager.getBucketByAddress(ctx.channel().remoteAddress() as InetSocketAddress)?.let(buckets::add)
|
||||||
|
}
|
||||||
|
|
||||||
var nextAttempt = -1L
|
var nextAttempt = -1L
|
||||||
for (bucket in buckets) {
|
for (bucket in buckets) {
|
||||||
val bucketNextAttempt = bucket.removeTokensWithEstimate(1)
|
val bucketNextAttempt = bucket.removeTokensWithEstimate(1)
|
||||||
@@ -78,38 +207,19 @@ class ThrottlingHandler(private val bucketManager : BucketManager,
|
|||||||
nextAttempt = bucketNextAttempt
|
nextAttempt = bucketNextAttempt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nextAttempt < 0) {
|
return nextAttempt
|
||||||
super.channelRead(ctx, msg)
|
|
||||||
queuedContent?.let {
|
|
||||||
for(content in it) {
|
|
||||||
super.channelRead(ctx, content)
|
|
||||||
}
|
|
||||||
queuedContent = null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val waitDuration = Duration.of(LongMath.ceilDiv(nextAttempt, 100_000_000L) * 100L, ChronoUnit.MILLIS)
|
|
||||||
if (delayResponse && waitDuration < waitThreshold) {
|
|
||||||
this.queuedContent = mutableListOf()
|
|
||||||
ctx.executor().schedule({
|
|
||||||
handleBuckets(buckets, ctx, msg, false)
|
|
||||||
}, waitDuration.toMillis(), TimeUnit.MILLISECONDS)
|
|
||||||
} else {
|
|
||||||
this.queuedContent = null
|
|
||||||
sendThrottledResponse(ctx, waitDuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendThrottledResponse(ctx: ChannelHandlerContext, retryAfter: Duration) {
|
private fun sendThrottledResponse(ctx: ChannelHandlerContext, retryAfter: Duration?) {
|
||||||
val response = DefaultFullHttpResponse(
|
val response = DefaultFullHttpResponse(
|
||||||
HttpVersion.HTTP_1_1,
|
HttpVersion.HTTP_1_1,
|
||||||
HttpResponseStatus.TOO_MANY_REQUESTS
|
HttpResponseStatus.TOO_MANY_REQUESTS
|
||||||
)
|
)
|
||||||
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
response.headers()[HttpHeaderNames.CONTENT_LENGTH] = 0
|
||||||
retryAfter.seconds.takeIf {
|
retryAfter?.seconds?.takeIf {
|
||||||
it > 0
|
it > 0
|
||||||
}?.let {
|
}?.let {
|
||||||
response.headers()[HttpHeaderNames.RETRY_AFTER] = retryAfter.seconds
|
response.headers()[HttpHeaderNames.RETRY_AFTER] = it
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.writeAndFlush(response)
|
ctx.writeAndFlush(response)
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
<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="rate-limiter" type="rbcs:rateLimiterType" 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:annotation>
|
||||||
<xs:documentation>
|
<xs:documentation>
|
||||||
@@ -115,6 +116,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">
|
||||||
@@ -128,6 +137,37 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="rateLimiterType">
|
||||||
|
<xs:attribute name="delay-response" type="xs:boolean" use="optional" default="false">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
If set to true, the server will delay responses to meet user quotas, otherwise it will simply
|
||||||
|
return an immediate 429 status code to all requests that exceed the configured quota
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="max-queued-messages" type="xs:nonNegativeInteger" use="optional" default="100">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Only meaningful when "delay-response" is set to "true",
|
||||||
|
when a request is delayed, it and all the following messages are queued
|
||||||
|
as long as "max-queued-messages" is not crossed, all requests that would exceed the
|
||||||
|
max-queued-message limit are instead discarded and responded with a 429 status code
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="message-buffer-size" type="rbcs:byteSizeType" use="optional" default="0x100000">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Only meaningful when "delay-response" is set to "true",
|
||||||
|
when a request is delayed, it and all the following requests are buffered
|
||||||
|
as long as "message-buffer-size" is not crossed, all requests that would exceed the buffer
|
||||||
|
size are instead discarded and responded with a 429 status code
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="cacheType" abstract="true"/>
|
<xs:complexType name="cacheType" abstract="true"/>
|
||||||
|
|
||||||
<xs:complexType name="inMemoryCacheType">
|
<xs:complexType name="inMemoryCacheType">
|
||||||
@@ -175,13 +215,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 +264,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>
|
||||||
|
@@ -1,11 +1,5 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
import net.woggioni.rbcs.api.Configuration
|
|
||||||
import net.woggioni.rbcs.api.Role
|
|
||||||
import net.woggioni.rbcs.common.RBCS.getFreePort
|
|
||||||
import net.woggioni.rbcs.common.Xml
|
|
||||||
import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration
|
|
||||||
import net.woggioni.rbcs.server.configuration.Serializer
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.http.HttpRequest
|
import java.net.http.HttpRequest
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
@@ -15,6 +9,12 @@ import java.time.temporal.ChronoUnit
|
|||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import net.woggioni.rbcs.api.Configuration
|
||||||
|
import net.woggioni.rbcs.api.Role
|
||||||
|
import net.woggioni.rbcs.common.RBCS.getFreePort
|
||||||
|
import net.woggioni.rbcs.common.Xml
|
||||||
|
import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration
|
||||||
|
import net.woggioni.rbcs.server.configuration.Serializer
|
||||||
|
|
||||||
|
|
||||||
abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
||||||
@@ -37,11 +37,13 @@ abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
|||||||
50,
|
50,
|
||||||
serverPath,
|
serverPath,
|
||||||
Configuration.EventExecutor(false),
|
Configuration.EventExecutor(false),
|
||||||
|
Configuration.RateLimiter(true, 0x100000, 50),
|
||||||
Configuration.Connection(
|
Configuration.Connection(
|
||||||
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 +52,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,
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.server.RemoteBuildCacheServer
|
import net.woggioni.rbcs.server.RemoteBuildCacheServer
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
@@ -8,7 +9,6 @@ import org.junit.jupiter.api.MethodOrderer
|
|||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.TestMethodOrder
|
import org.junit.jupiter.api.TestMethodOrder
|
||||||
import org.junit.jupiter.api.io.TempDir
|
import org.junit.jupiter.api.io.TempDir
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
@@ -1,14 +1,5 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
import net.woggioni.rbcs.api.Configuration
|
|
||||||
import net.woggioni.rbcs.api.Role
|
|
||||||
import net.woggioni.rbcs.common.RBCS.getFreePort
|
|
||||||
import net.woggioni.rbcs.common.Xml
|
|
||||||
import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration
|
|
||||||
import net.woggioni.rbcs.server.configuration.Serializer
|
|
||||||
import net.woggioni.rbcs.server.test.utils.CertificateUtils
|
|
||||||
import net.woggioni.rbcs.server.test.utils.CertificateUtils.X509Credentials
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.http.HttpClient
|
import java.net.http.HttpClient
|
||||||
import java.net.http.HttpRequest
|
import java.net.http.HttpRequest
|
||||||
@@ -25,6 +16,15 @@ import javax.net.ssl.KeyManagerFactory
|
|||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.TrustManagerFactory
|
import javax.net.ssl.TrustManagerFactory
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import net.woggioni.rbcs.api.Configuration
|
||||||
|
import net.woggioni.rbcs.api.Role
|
||||||
|
import net.woggioni.rbcs.common.RBCS.getFreePort
|
||||||
|
import net.woggioni.rbcs.common.Xml
|
||||||
|
import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration
|
||||||
|
import net.woggioni.rbcs.server.configuration.Serializer
|
||||||
|
import net.woggioni.rbcs.server.test.utils.CertificateUtils
|
||||||
|
import net.woggioni.rbcs.server.test.utils.CertificateUtils.X509Credentials
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
|
|
||||||
|
|
||||||
abstract class AbstractTlsServerTest : AbstractServerTest() {
|
abstract class AbstractTlsServerTest : AbstractServerTest() {
|
||||||
@@ -143,11 +143,13 @@ abstract class AbstractTlsServerTest : AbstractServerTest() {
|
|||||||
100,
|
100,
|
||||||
serverPath,
|
serverPath,
|
||||||
Configuration.EventExecutor(false),
|
Configuration.EventExecutor(false),
|
||||||
|
Configuration.RateLimiter(true, 0x100000, 50),
|
||||||
Configuration.Connection(
|
Configuration.Connection(
|
||||||
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 +158,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),
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
|
import java.net.http.HttpClient
|
||||||
|
import java.net.http.HttpRequest
|
||||||
|
import java.net.http.HttpResponse
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.api.Role
|
import net.woggioni.rbcs.api.Role
|
||||||
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Order
|
import org.junit.jupiter.api.Order
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.net.http.HttpClient
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.net.http.HttpResponse
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
|
|
||||||
|
|
||||||
class BasicAuthServerTest : AbstractBasicAuthServerTest() {
|
class BasicAuthServerTest : AbstractBasicAuthServerTest() {
|
||||||
@@ -154,7 +154,7 @@ class BasicAuthServerTest : AbstractBasicAuthServerTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(6)
|
@Order(8)
|
||||||
fun getAsAThrottledUser() {
|
fun getAsAThrottledUser() {
|
||||||
val client: HttpClient = HttpClient.newHttpClient()
|
val client: HttpClient = HttpClient.newHttpClient()
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ class BasicAuthServerTest : AbstractBasicAuthServerTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(7)
|
@Order(9)
|
||||||
fun getAsAThrottledUser2() {
|
fun getAsAThrottledUser2() {
|
||||||
val client: HttpClient = HttpClient.newHttpClient()
|
val client: HttpClient = HttpClient.newHttpClient()
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
import net.woggioni.rbcs.common.RBCS.toUrl
|
import net.woggioni.rbcs.common.RBCS.toUrl
|
||||||
import net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory
|
import net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory
|
||||||
import net.woggioni.rbcs.common.Xml
|
import net.woggioni.rbcs.common.Xml
|
||||||
@@ -10,8 +12,6 @@ import org.junit.jupiter.api.io.TempDir
|
|||||||
import org.junit.jupiter.params.ParameterizedTest
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
import org.junit.jupiter.params.provider.ValueSource
|
import org.junit.jupiter.params.provider.ValueSource
|
||||||
import org.xml.sax.SAXParseException
|
import org.xml.sax.SAXParseException
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
class ConfigurationTest {
|
class ConfigurationTest {
|
||||||
|
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
|
import java.net.http.HttpClient
|
||||||
|
import java.net.http.HttpRequest
|
||||||
|
import java.net.http.HttpResponse
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Order
|
import org.junit.jupiter.api.Order
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.net.http.HttpClient
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.net.http.HttpResponse
|
|
||||||
|
|
||||||
|
|
||||||
class NoAnonymousUserBasicAuthServerTest : AbstractBasicAuthServerTest() {
|
class NoAnonymousUserBasicAuthServerTest : AbstractBasicAuthServerTest() {
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
|
import java.net.http.HttpClient
|
||||||
|
import java.net.http.HttpRequest
|
||||||
|
import java.net.http.HttpResponse
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Order
|
import org.junit.jupiter.api.Order
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.net.http.HttpClient
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.net.http.HttpResponse
|
|
||||||
|
|
||||||
class NoAnonymousUserTlsServerTest : AbstractTlsServerTest() {
|
class NoAnonymousUserTlsServerTest : AbstractTlsServerTest() {
|
||||||
|
|
||||||
|
@@ -1,14 +1,6 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
import net.woggioni.rbcs.api.Configuration
|
|
||||||
import net.woggioni.rbcs.common.RBCS.getFreePort
|
|
||||||
import net.woggioni.rbcs.common.Xml
|
|
||||||
import net.woggioni.rbcs.server.cache.InMemoryCacheConfiguration
|
|
||||||
import net.woggioni.rbcs.server.configuration.Serializer
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.Order
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.http.HttpClient
|
import java.net.http.HttpClient
|
||||||
import java.net.http.HttpRequest
|
import java.net.http.HttpRequest
|
||||||
@@ -19,6 +11,14 @@ import java.time.temporal.ChronoUnit
|
|||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import net.woggioni.rbcs.api.Configuration
|
||||||
|
import net.woggioni.rbcs.common.RBCS.getFreePort
|
||||||
|
import net.woggioni.rbcs.common.Xml
|
||||||
|
import net.woggioni.rbcs.server.cache.InMemoryCacheConfiguration
|
||||||
|
import net.woggioni.rbcs.server.configuration.Serializer
|
||||||
|
import org.junit.jupiter.api.Assertions
|
||||||
|
import org.junit.jupiter.api.Order
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
class NoAuthServerTest : AbstractServerTest() {
|
class NoAuthServerTest : AbstractServerTest() {
|
||||||
@@ -37,11 +37,13 @@ class NoAuthServerTest : AbstractServerTest() {
|
|||||||
100,
|
100,
|
||||||
serverPath,
|
serverPath,
|
||||||
Configuration.EventExecutor(false),
|
Configuration.EventExecutor(false),
|
||||||
|
Configuration.RateLimiter(true, 0x100000, 50),
|
||||||
Configuration.Connection(
|
Configuration.Connection(
|
||||||
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 +53,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,
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
import io.netty.handler.codec.http.HttpResponseStatus
|
||||||
|
import java.net.http.HttpClient
|
||||||
|
import java.net.http.HttpRequest
|
||||||
|
import java.net.http.HttpResponse
|
||||||
import net.woggioni.rbcs.api.Configuration
|
import net.woggioni.rbcs.api.Configuration
|
||||||
import net.woggioni.rbcs.api.Role
|
import net.woggioni.rbcs.api.Role
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Order
|
import org.junit.jupiter.api.Order
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.net.http.HttpClient
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.net.http.HttpResponse
|
|
||||||
|
|
||||||
|
|
||||||
class TlsServerTest : AbstractTlsServerTest() {
|
class TlsServerTest : AbstractTlsServerTest() {
|
||||||
@@ -166,4 +166,17 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
|
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
|
||||||
println(String(response.body()))
|
println(String(response.body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(10)
|
||||||
|
fun putAsUnknownUserUser() {
|
||||||
|
val (key, value) = keyValuePair
|
||||||
|
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=Unknown user")))
|
||||||
|
val requestBuilder = newRequestBuilder(key)
|
||||||
|
.header("Content-Type", "application/octet-stream")
|
||||||
|
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
||||||
|
|
||||||
|
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
||||||
|
Assertions.assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), response.statusCode())
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
package net.woggioni.rbcs.server.test
|
package net.woggioni.rbcs.server.test
|
||||||
|
|
||||||
|
import javax.naming.ldap.LdapName
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import javax.naming.ldap.LdapName
|
|
||||||
|
|
||||||
class X500NameTest {
|
class X500NameTest {
|
||||||
|
|
||||||
|
@@ -7,9 +7,11 @@
|
|||||||
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"/>
|
<rate-limiter delay-response="false" message-buffer-size="0x1234" max-queued-messages="13"/>
|
||||||
|
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D"/>
|
||||||
<authentication>
|
<authentication>
|
||||||
<none/>
|
<none/>
|
||||||
</authentication>
|
</authentication>
|
||||||
|
@@ -9,9 +9,11 @@
|
|||||||
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">
|
<rate-limiter delay-response="false" message-buffer-size="12000" max-queued-messages="53"/>
|
||||||
|
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" key-prefix="some-prefix-string">
|
||||||
<server host="memcached" port="11211"/>
|
<server host="memcached" port="11211"/>
|
||||||
</cache>
|
</cache>
|
||||||
<authorization>
|
<authorization>
|
||||||
|
@@ -8,9 +8,11 @@
|
|||||||
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">
|
<rate-limiter delay-response="true" message-buffer-size="65432" max-queued-messages="21"/>
|
||||||
|
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" key-prefix="some-prefix-string" 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">
|
||||||
|
@@ -53,7 +53,9 @@
|
|||||||
<!-- <Connector port="8080" protocol="HTTP/1.1" executor="tomcatThreadPool"-->
|
<!-- <Connector port="8080" protocol="HTTP/1.1" executor="tomcatThreadPool"-->
|
||||||
<!-- connectionTimeout="20000"-->
|
<!-- connectionTimeout="20000"-->
|
||||||
<!-- redirectPort="8443" />-->
|
<!-- redirectPort="8443" />-->
|
||||||
<Connector port="8080" protocol="HTTP/1.1"
|
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
|
||||||
|
maxKeepAliveRequests="1000000"
|
||||||
|
keepAliveTimeout="-1"
|
||||||
connectionTimeout="20000"
|
connectionTimeout="20000"
|
||||||
redirectPort="8443" />
|
redirectPort="8443" />
|
||||||
<!-- A "Connector" using the shared thread pool-->
|
<!-- A "Connector" using the shared thread pool-->
|
||||||
|
@@ -7,8 +7,6 @@ import jakarta.servlet.annotation.WebServlet
|
|||||||
import jakarta.servlet.http.HttpServlet
|
import jakarta.servlet.http.HttpServlet
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import net.woggioni.jwo.HttpClient.HttpStatus
|
|
||||||
import net.woggioni.jwo.JWO
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@@ -19,6 +17,8 @@ import java.util.concurrent.PriorityBlockingQueue
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
import net.woggioni.jwo.HttpClient.HttpStatus
|
||||||
|
import net.woggioni.jwo.JWO
|
||||||
|
|
||||||
|
|
||||||
private class CacheKey(private val value: ByteArray) {
|
private class CacheKey(private val value: ByteArray) {
|
||||||
|
Reference in New Issue
Block a user