Compare commits

...

2 Commits

Author SHA1 Message Date
woggioni fc9900d821 fixed bug in the server configuration parser
CI / build (push) Successful in 2m50s
added Jacoco test report
2025-01-20 20:23:09 +08:00
woggioni 1a78c8092b fixed client bug (unhandled connection touts)
CI / build (push) Successful in 3m7s
2025-01-20 19:18:20 +08:00
9 changed files with 84 additions and 28 deletions
+9
View File
@@ -66,6 +66,15 @@ allprojects { subproject ->
}
}
pluginManager.withPlugin('jacoco') {
test {
finalizedBy jacocoTestReport
}
jacocoTestReport {
dependsOn test
}
}
pluginManager.withPlugin(catalog.plugins.kotlin.jvm.get().pluginId) {
tasks.withType(KotlinCompile.class) {
compilerOptions.jvmTarget = JvmTarget.JVM_21
@@ -46,7 +46,8 @@ class BenchmarkCommand : GbcsCommand() {
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
while (true) {
val key = Base64.getUrlEncoder().encode(random.nextBytes(16)).toString(Charsets.UTF_8)
val value = random.nextBytes(0x1000)
val content = random.nextInt().toByte()
val value = ByteArray(0x1000, { _ -> content })
yield(key to value)
}
}
@@ -30,16 +30,18 @@ import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.stream.ChunkedWriteHandler
import io.netty.util.concurrent.Future
import io.netty.util.concurrent.GenericFutureListener
import net.woggioni.gbcs.client.impl.Parser
import net.woggioni.gbcs.common.Xml
import net.woggioni.gbcs.common.contextLogger
import net.woggioni.gbcs.common.debug
import net.woggioni.gbcs.client.impl.Parser
import net.woggioni.gbcs.common.trace
import java.net.InetSocketAddress
import java.net.URI
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.util.Base64
import java.util.concurrent.CompletableFuture
import java.util.concurrent.atomic.AtomicInteger
@@ -67,6 +69,7 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
data class Profile(
val serverURI: URI,
val authentication: Authentication?,
val connectionTimeout : Duration?,
val maxConnections : Int
)
@@ -104,6 +107,9 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
option(ChannelOption.TCP_NODELAY, true)
option(ChannelOption.SO_KEEPALIVE, true)
remoteAddress(InetSocketAddress(host, port))
profile.connectionTimeout?.let {
option(ChannelOption.CONNECT_TIMEOUT_MILLIS, it.toMillis().toInt())
}
}
val channelPoolHandler = object : AbstractChannelPoolHandler() {
@@ -114,20 +120,29 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
private var leaseCount = AtomicInteger()
override fun channelReleased(ch: Channel) {
log.debug {
"Released lease ${leaseCount.decrementAndGet()}"
val activeLeases = leaseCount.decrementAndGet()
log.trace {
"Released channel ${ch.id().asShortText()}, number of active leases: $activeLeases"
}
}
override fun channelAcquired(ch: Channel?) {
log.debug {
"Acquired lease ${leaseCount.getAndIncrement()}"
override fun channelAcquired(ch: Channel) {
val activeLeases = leaseCount.getAndIncrement()
log.trace {
"Acquired channel ${ch.id().asShortText()}, number of active leases: $activeLeases"
}
}
override fun channelCreated(ch: Channel) {
val connectionId = connectionCount.getAndIncrement()
log.debug {
"Created connection ${connectionCount.getAndIncrement()}"
"Created connection $connectionId, total number of active connections: $connectionId"
}
ch.closeFuture().addListener {
val activeConnections = connectionCount.decrementAndGet()
log.debug {
"Closed connection $connectionId, total number of active connections: $activeConnections"
}
}
val pipeline: ChannelPipeline = ch.pipeline()
@@ -202,6 +217,7 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
ctx.close()
pipeline.removeLast()
pool.release(channel)
super.exceptionCaught(ctx, cause)
}
})
// Prepare the HTTP request
@@ -219,7 +235,7 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes())
}
set(HttpHeaderNames.HOST, profile.serverURI.host)
set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE)
set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE)
set(
HttpHeaderNames.ACCEPT_ENCODING,
HttpHeaderValues.GZIP.toString() + "," + HttpHeaderValues.DEFLATE.toString()
@@ -237,6 +253,8 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
// Set headers
// Send the request
channel.writeAndFlush(request)
} else {
responseFuture.completeExceptionally(channelFuture.cause())
}
}
})
@@ -11,6 +11,7 @@ import java.nio.file.Path
import java.security.KeyStore
import java.security.PrivateKey
import java.security.cert.X509Certificate
import java.time.Duration
object Parser {
@@ -60,7 +61,9 @@ object Parser {
val maxConnections = child.renderAttribute("max-connections")
?.let(String::toInt)
?: 50
profiles[name] = GbcsClient.Configuration.Profile(uri, authentication, maxConnections)
val connectionTimeout = child.renderAttribute("connection-timeout")
?.let(Duration::parse)
profiles[name] = GbcsClient.Configuration.Profile(uri, authentication, connectionTimeout, maxConnections)
}
}
}
@@ -21,6 +21,7 @@
<xs:attribute name="name" type="xs:token" use="required"/>
<xs:attribute name="base-url" type="xs:anyURI" use="required"/>
<xs:attribute name="max-connections" type="xs:positiveInteger" default="50"/>
<xs:attribute name="connection-timeout" type="xs:duration"/>
</xs:complexType>
<xs:complexType name="noAuthType"/>
+1
View File
@@ -1,6 +1,7 @@
plugins {
id 'java-library'
alias catalog.plugins.kotlin.jvm
id 'jacoco'
id 'maven-publish'
}
@@ -20,7 +20,8 @@ import javax.net.ssl.X509TrustManager
class ClientCertificateValidator private constructor(
private val sslHandler: SslHandler,
private val x509TrustManager: X509TrustManager) : ChannelInboundHandlerAdapter() {
private val x509TrustManager: X509TrustManager
) : ChannelInboundHandlerAdapter() {
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
if (evt is SslHandshakeCompletionEvent) {
if (evt.isSuccess) {
@@ -42,7 +43,8 @@ class ClientCertificateValidator private constructor(
val validator = CertPathValidator.getInstance("PKIX").apply {
val rc = revocationChecker as PKIXRevocationChecker
rc.options = EnumSet.of(
PKIXRevocationChecker.Option.NO_FALLBACK)
PKIXRevocationChecker.Option.NO_FALLBACK
)
}
val params = PKIXParameters(trustStore).apply {
isRevocationEnabled = certificateRevocationEnabled
@@ -72,11 +74,16 @@ class ClientCertificateValidator private constructor(
}
} else {
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.trustManagers.asSequence().filter { it is X509TrustManager }.single() as X509TrustManager
trustManagerFactory.trustManagers.asSequence().filter { it is X509TrustManager }
.single() as X509TrustManager
}
}
fun of(sslHandler : SslHandler, trustStore : KeyStore?, certificateRevocationEnabled : Boolean) : ClientCertificateValidator {
fun of(
sslHandler: SslHandler,
trustStore: KeyStore?,
certificateRevocationEnabled: Boolean
): ClientCertificateValidator {
return ClientCertificateValidator(sslHandler, getTrustManager(trustStore, certificateRevocationEnabled))
}
}
@@ -21,14 +21,20 @@ import org.w3c.dom.TypeInfo
import java.nio.file.Paths
import java.time.Duration
import java.time.temporal.ChronoUnit
import java.util.concurrent.TimeUnit
object Parser {
fun parse(document: Document): Configuration {
val root = document.documentElement
val anonymousUser = User("", null, emptySet())
var connection: Configuration.Connection? = null
var eventExecutor: Configuration.EventExecutor? = null
var connection: Configuration.Connection = Configuration.Connection(
Duration.of(10, ChronoUnit.SECONDS),
Duration.of(10, ChronoUnit.SECONDS),
Duration.of(60, ChronoUnit.SECONDS),
Duration.of(30, ChronoUnit.SECONDS),
Duration.of(30, ChronoUnit.SECONDS),
67108864
)
var eventExecutor: Configuration.EventExecutor = Configuration.EventExecutor(true)
var cache: Cache? = null
var host = "127.0.0.1"
var port = 11080
@@ -194,8 +200,12 @@ object Parser {
}.toSet()
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().map {
it.renderAttribute("ref")
}.toSet()
when(it.localName) {
"user" -> it.renderAttribute("ref")
"anonymous" -> ""
else -> ConfigurationException("Unrecognized tag '${it.localName}'")
}
}
private fun parseUsers(root: Element): Sequence<User> {
return root.asIterable().asSequence().filter {
@@ -1,11 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<gbcs:server use-virtual-threads="false"
max-request-size="67108864"
incoming-connections-backlog-size="1024"
<gbcs:server
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gbcs="urn:net.woggioni.gbcs.server"
xs:schemaLocation="urn:net.woggioni.gbcs.server jpms://net.woggioni.gbcs.server/net/woggioni/gbcs/server/schema/gbcs.xsd">
<bind host="127.0.0.1" port="8080"/>
<bind host="127.0.0.1" port="8080" incoming-connections-backlog-size="1024"/>
<connection
max-request-size="67108864"
idle-timeout="PT30S"
read-timeout="PT10S"
write-timeout="PT10S"
read-idle-timeout="PT60S"
write-idle-timeout="PT60S"/>
<event-executor use-virtual-threads="true"/>
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
<authentication>
<none/>