Compare commits
2 Commits
3d1847c408
...
fc9900d821
Author | SHA1 | Date | |
---|---|---|---|
fc9900d821
|
|||
1a78c8092b
|
@@ -66,6 +66,15 @@ allprojects { subproject ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pluginManager.withPlugin('jacoco') {
|
||||||
|
test {
|
||||||
|
finalizedBy jacocoTestReport
|
||||||
|
}
|
||||||
|
jacocoTestReport {
|
||||||
|
dependsOn test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pluginManager.withPlugin(catalog.plugins.kotlin.jvm.get().pluginId) {
|
pluginManager.withPlugin(catalog.plugins.kotlin.jvm.get().pluginId) {
|
||||||
tasks.withType(KotlinCompile.class) {
|
tasks.withType(KotlinCompile.class) {
|
||||||
compilerOptions.jvmTarget = JvmTarget.JVM_21
|
compilerOptions.jvmTarget = JvmTarget.JVM_21
|
||||||
|
@@ -46,7 +46,8 @@ class BenchmarkCommand : GbcsCommand() {
|
|||||||
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
|
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
|
||||||
while (true) {
|
while (true) {
|
||||||
val key = Base64.getUrlEncoder().encode(random.nextBytes(16)).toString(Charsets.UTF_8)
|
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)
|
yield(key to value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,16 +30,18 @@ import io.netty.handler.ssl.SslContextBuilder
|
|||||||
import io.netty.handler.stream.ChunkedWriteHandler
|
import io.netty.handler.stream.ChunkedWriteHandler
|
||||||
import io.netty.util.concurrent.Future
|
import io.netty.util.concurrent.Future
|
||||||
import io.netty.util.concurrent.GenericFutureListener
|
import io.netty.util.concurrent.GenericFutureListener
|
||||||
|
import net.woggioni.gbcs.client.impl.Parser
|
||||||
import net.woggioni.gbcs.common.Xml
|
import net.woggioni.gbcs.common.Xml
|
||||||
import net.woggioni.gbcs.common.contextLogger
|
import net.woggioni.gbcs.common.contextLogger
|
||||||
import net.woggioni.gbcs.common.debug
|
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.InetSocketAddress
|
||||||
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.util.Base64
|
import java.util.Base64
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
@@ -67,6 +69,7 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
|
|||||||
data class Profile(
|
data class Profile(
|
||||||
val serverURI: URI,
|
val serverURI: URI,
|
||||||
val authentication: Authentication?,
|
val authentication: Authentication?,
|
||||||
|
val connectionTimeout : Duration?,
|
||||||
val maxConnections : Int
|
val maxConnections : Int
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,6 +107,9 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
|
|||||||
option(ChannelOption.TCP_NODELAY, true)
|
option(ChannelOption.TCP_NODELAY, true)
|
||||||
option(ChannelOption.SO_KEEPALIVE, true)
|
option(ChannelOption.SO_KEEPALIVE, true)
|
||||||
remoteAddress(InetSocketAddress(host, port))
|
remoteAddress(InetSocketAddress(host, port))
|
||||||
|
profile.connectionTimeout?.let {
|
||||||
|
option(ChannelOption.CONNECT_TIMEOUT_MILLIS, it.toMillis().toInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val channelPoolHandler = object : AbstractChannelPoolHandler() {
|
val channelPoolHandler = object : AbstractChannelPoolHandler() {
|
||||||
|
|
||||||
@@ -114,20 +120,29 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
|
|||||||
private var leaseCount = AtomicInteger()
|
private var leaseCount = AtomicInteger()
|
||||||
|
|
||||||
override fun channelReleased(ch: Channel) {
|
override fun channelReleased(ch: Channel) {
|
||||||
log.debug {
|
val activeLeases = leaseCount.decrementAndGet()
|
||||||
"Released lease ${leaseCount.decrementAndGet()}"
|
log.trace {
|
||||||
|
"Released channel ${ch.id().asShortText()}, number of active leases: $activeLeases"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun channelAcquired(ch: Channel?) {
|
override fun channelAcquired(ch: Channel) {
|
||||||
log.debug {
|
val activeLeases = leaseCount.getAndIncrement()
|
||||||
"Acquired lease ${leaseCount.getAndIncrement()}"
|
log.trace {
|
||||||
|
"Acquired channel ${ch.id().asShortText()}, number of active leases: $activeLeases"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun channelCreated(ch: Channel) {
|
override fun channelCreated(ch: Channel) {
|
||||||
|
val connectionId = connectionCount.getAndIncrement()
|
||||||
log.debug {
|
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()
|
val pipeline: ChannelPipeline = ch.pipeline()
|
||||||
|
|
||||||
@@ -202,6 +217,7 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
|
|||||||
ctx.close()
|
ctx.close()
|
||||||
pipeline.removeLast()
|
pipeline.removeLast()
|
||||||
pool.release(channel)
|
pool.release(channel)
|
||||||
|
super.exceptionCaught(ctx, cause)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Prepare the HTTP request
|
// Prepare the HTTP request
|
||||||
@@ -219,7 +235,7 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
|
|||||||
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.CLOSE)
|
set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE)
|
||||||
set(
|
set(
|
||||||
HttpHeaderNames.ACCEPT_ENCODING,
|
HttpHeaderNames.ACCEPT_ENCODING,
|
||||||
HttpHeaderValues.GZIP.toString() + "," + HttpHeaderValues.DEFLATE.toString()
|
HttpHeaderValues.GZIP.toString() + "," + HttpHeaderValues.DEFLATE.toString()
|
||||||
@@ -237,6 +253,8 @@ class GbcsClient(private val profile: Configuration.Profile) : AutoCloseable {
|
|||||||
// Set headers
|
// Set headers
|
||||||
// Send the request
|
// Send the request
|
||||||
channel.writeAndFlush(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.KeyStore
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
object Parser {
|
object Parser {
|
||||||
|
|
||||||
@@ -60,7 +61,9 @@ object Parser {
|
|||||||
val maxConnections = child.renderAttribute("max-connections")
|
val maxConnections = child.renderAttribute("max-connections")
|
||||||
?.let(String::toInt)
|
?.let(String::toInt)
|
||||||
?: 50
|
?: 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="name" type="xs:token" use="required"/>
|
||||||
<xs:attribute name="base-url" type="xs:anyURI" 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="max-connections" type="xs:positiveInteger" default="50"/>
|
||||||
|
<xs:attribute name="connection-timeout" type="xs:duration"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="noAuthType"/>
|
<xs:complexType name="noAuthType"/>
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
alias catalog.plugins.kotlin.jvm
|
alias catalog.plugins.kotlin.jvm
|
||||||
|
id 'jacoco'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,8 +19,9 @@ import javax.net.ssl.X509TrustManager
|
|||||||
|
|
||||||
|
|
||||||
class ClientCertificateValidator private constructor(
|
class ClientCertificateValidator private constructor(
|
||||||
private val sslHandler : SslHandler,
|
private val sslHandler: SslHandler,
|
||||||
private val x509TrustManager: X509TrustManager) : ChannelInboundHandlerAdapter() {
|
private val x509TrustManager: X509TrustManager
|
||||||
|
) : ChannelInboundHandlerAdapter() {
|
||||||
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
||||||
if (evt is SslHandshakeCompletionEvent) {
|
if (evt is SslHandshakeCompletionEvent) {
|
||||||
if (evt.isSuccess) {
|
if (evt.isSuccess) {
|
||||||
@@ -36,13 +37,14 @@ class ClientCertificateValidator private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getTrustManager(trustStore : KeyStore?, certificateRevocationEnabled : Boolean) : X509TrustManager {
|
fun getTrustManager(trustStore: KeyStore?, certificateRevocationEnabled: Boolean): X509TrustManager {
|
||||||
return if(trustStore != null) {
|
return if (trustStore != null) {
|
||||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||||
val validator = CertPathValidator.getInstance("PKIX").apply {
|
val validator = CertPathValidator.getInstance("PKIX").apply {
|
||||||
val rc = revocationChecker as PKIXRevocationChecker
|
val rc = revocationChecker as PKIXRevocationChecker
|
||||||
rc.options = EnumSet.of(
|
rc.options = EnumSet.of(
|
||||||
PKIXRevocationChecker.Option.NO_FALLBACK)
|
PKIXRevocationChecker.Option.NO_FALLBACK
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val params = PKIXParameters(trustStore).apply {
|
val params = PKIXParameters(trustStore).apply {
|
||||||
isRevocationEnabled = certificateRevocationEnabled
|
isRevocationEnabled = certificateRevocationEnabled
|
||||||
@@ -52,7 +54,7 @@ class ClientCertificateValidator private constructor(
|
|||||||
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
|
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
|
||||||
try {
|
try {
|
||||||
validator.validate(clientCertificateChain, params)
|
validator.validate(clientCertificateChain, params)
|
||||||
} catch (ex : CertPathValidatorException) {
|
} catch (ex: CertPathValidatorException) {
|
||||||
throw CertificateException(ex)
|
throw CertificateException(ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,7 @@ class ClientCertificateValidator private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val acceptedIssuers = trustStore.aliases().asSequence()
|
private val acceptedIssuers = trustStore.aliases().asSequence()
|
||||||
.filter (trustStore::isCertificateEntry)
|
.filter(trustStore::isCertificateEntry)
|
||||||
.map(trustStore::getCertificate)
|
.map(trustStore::getCertificate)
|
||||||
.map { it as X509Certificate }
|
.map { it as X509Certificate }
|
||||||
.toList()
|
.toList()
|
||||||
@@ -72,11 +74,16 @@ class ClientCertificateValidator private constructor(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
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))
|
return ClientCertificateValidator(sslHandler, getTrustManager(trustStore, certificateRevocationEnabled))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,14 +21,20 @@ import org.w3c.dom.TypeInfo
|
|||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
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())
|
val anonymousUser = User("", null, emptySet())
|
||||||
var connection: Configuration.Connection? = null
|
var connection: Configuration.Connection = Configuration.Connection(
|
||||||
var eventExecutor: Configuration.EventExecutor? = null
|
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 cache: Cache? = null
|
||||||
var host = "127.0.0.1"
|
var host = "127.0.0.1"
|
||||||
var port = 11080
|
var port = 11080
|
||||||
@@ -194,8 +200,12 @@ object Parser {
|
|||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().map {
|
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().map {
|
||||||
it.renderAttribute("ref")
|
when(it.localName) {
|
||||||
}.toSet()
|
"user" -> it.renderAttribute("ref")
|
||||||
|
"anonymous" -> ""
|
||||||
|
else -> ConfigurationException("Unrecognized tag '${it.localName}'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseUsers(root: Element): Sequence<User> {
|
private fun parseUsers(root: Element): Sequence<User> {
|
||||||
return root.asIterable().asSequence().filter {
|
return root.asIterable().asSequence().filter {
|
||||||
|
@@ -1,11 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<gbcs:server use-virtual-threads="false"
|
<gbcs:server
|
||||||
max-request-size="67108864"
|
|
||||||
incoming-connections-backlog-size="1024"
|
|
||||||
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:gbcs="urn:net.woggioni.gbcs.server"
|
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">
|
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"/>
|
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
|
||||||
<authentication>
|
<authentication>
|
||||||
<none/>
|
<none/>
|
||||||
|
Reference in New Issue
Block a user