temporary commit

This commit is contained in:
2023-02-12 22:36:37 +08:00
parent 47ec94caf2
commit f28ecca45e
11 changed files with 409 additions and 198 deletions

View File

@@ -1,5 +1,20 @@
package net.woggioni.gbcs
import java.net.InetSocketAddress
import java.net.URL
import java.net.URLStreamHandlerFactory
import java.nio.channels.FileChannel
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.nio.file.StandardOpenOption
import java.security.KeyStore
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.cert.X509Certificate
import java.util.AbstractMap.SimpleEntry
import java.util.Base64
import java.util.ServiceLoader
import io.netty.bootstrap.ServerBootstrap
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
@@ -30,25 +45,18 @@ import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.handler.codec.http.HttpServerCodec
import io.netty.handler.codec.http.HttpUtil
import io.netty.handler.codec.http.LastHttpContent
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.util.SelfSignedCertificate
import io.netty.handler.stream.ChunkedNioFile
import io.netty.handler.stream.ChunkedWriteHandler
import io.netty.util.concurrent.DefaultEventExecutorGroup
import io.netty.util.concurrent.EventExecutorGroup
import net.woggioni.jwo.Application
import net.woggioni.jwo.JWO
import java.nio.channels.FileChannel
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.nio.file.StandardOpenOption
import java.security.MessageDigest
import java.util.AbstractMap.SimpleEntry
import java.util.Base64
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLEngine
import net.woggioni.jwo.Tuple2
class GradleBuildCacheServer {
class GradleBuildCacheServer(private val cfg : Configuration) {
internal class HttpChunkContentCompressor(threshold : Int, vararg compressionOptions: CompressionOptions = emptyArray())
: HttpContentCompressor(threshold, *compressionOptions) {
@@ -115,25 +123,62 @@ class GradleBuildCacheServer {
}
}
private class ServerInitializer(private val cacheDir: Path) : ChannelInitializer<Channel>() {
private class ServerInitializer(private val cfg : Configuration) : ChannelInitializer<Channel>() {
companion object {
val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
fun loadKeystore(file : Path, password : String?) : KeyStore {
val ext = JWO.splitExtension(file)
.map(Tuple2<String, String>::get_2)
.orElseThrow {
IllegalArgumentException(
"Keystore file '${file}' must have .jks or p12 extension")
}
val keystore = when(ext.lowercase()) {
"jks" -> KeyStore.getInstance("JKS")
"p12", "pfx" -> KeyStore.getInstance("PKCS12")
else -> throw IllegalArgumentException(
"Keystore file '${file}' must have .jks or p12 extension")
}
Files.newInputStream(file).use {
keystore.load(it, password?.let(String::toCharArray))
}
return keystore
}
}
override fun initChannel(ch: Channel) {
val sslEngine: SSLEngine = SSLContext.getDefault().createSSLEngine()
sslEngine.useClientMode = false
val pipeline = ch.pipeline()
// pipeline.addLast(SslHandler(sslEngine))
val tlsConfiguration = cfg.tlsConfiguration
if(tlsConfiguration != null) {
val ssc = SelfSignedCertificate()
val keyStore = tlsConfiguration.keyStore
val sslCtx = if(keyStore == null) {
SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build()
} else {
val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
val serverKey = javaKeyStore.getKey(
keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray)) as PrivateKey
val serverCert = javaKeyStore.getCertificateChain(keyStore.keyAlias) as Array<X509Certificate>
SslContextBuilder.forServer(serverKey, *serverCert).build()
}
val sslHandler = sslCtx.newHandler(ch.alloc())
pipeline.addLast(sslHandler)
if(tlsConfiguration.verifyClients) {
val trustStore = tlsConfiguration.trustStore?.let {
loadKeystore(it.file, it.password)
}
pipeline.addLast(ClientCertificateValidator.of(sslHandler, trustStore))
}
}
pipeline.addLast(HttpServerCodec())
pipeline.addLast(HttpChunkContentCompressor(1024))
pipeline.addLast(ChunkedWriteHandler())
pipeline.addLast(HttpObjectAggregator(Int.MAX_VALUE))
pipeline.addLast(NettyHttpBasicAuthenticator(mapOf("user" to "password")) { user, _ -> user == "user" })
pipeline.addLast(group, ServerHandler(cacheDir, "/cache"))
// pipeline.addLast(NettyHttpBasicAuthenticator(mapOf("user" to "password")) { user, _ -> user == "user" })
pipeline.addLast(group, ServerHandler(cfg.cacheFolder, cfg.serverPath))
pipeline.addLast(ExceptionHandler())
Files.createDirectories(cacheDir)
}
companion object {
val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
Files.createDirectories(cfg.cacheFolder)
}
}
@@ -254,12 +299,13 @@ class GradleBuildCacheServer {
// Configure the server
httpBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel::class.java)
.childHandler(ServerInitializer(Paths.get("/tmp/gbcs"))) // <-- Our handler created here
.childHandler(ServerInitializer(cfg))
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
// Bind and start to accept incoming connections.
val httpChannel = httpBootstrap.bind(HTTP_PORT).sync()
val bindAddress = InetSocketAddress(cfg.host, cfg.port)
val httpChannel = httpBootstrap.bind(bindAddress).sync()
// Wait until server socket is closed
httpChannel.channel().closeFuture().sync()
@@ -270,11 +316,69 @@ class GradleBuildCacheServer {
}
companion object {
private const val HTTP_PORT = 8080
private const val PROTOCOL_HANDLER = "java.protocol.handler.pkgs"
private const val HANDLERS_PACKAGE = "net.woggioni.gbcs.url"
/**
* Reset any cached handlers just in case a jar protocol has already been used. We
* reset the handler by trying to set a null [URLStreamHandlerFactory] which
* should have no effect other than clearing the handlers cache.
*/
private fun resetCachedUrlHandlers() {
try {
URL.setURLStreamHandlerFactory(null)
} catch (ex: Error) {
// Ignore
}
}
fun registerUrlProtocolHandler() {
val handlers = System.getProperty(PROTOCOL_HANDLER, "")
System.setProperty(
PROTOCOL_HANDLER,
if (handlers == null || handlers.isEmpty()) HANDLERS_PACKAGE else "$handlers|$HANDLERS_PACKAGE"
)
resetCachedUrlHandlers()
}
@JvmStatic
fun main(args: Array<String>) {
SelfSignedCertificate()
ServiceLoader.load(javaClass.module.layer, URLStreamHandlerFactory::class.java).stream().forEach {
println(it.type())
}
// registerUrlProtocolHandler()
Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
GradleBuildCacheServer().run()
val app = Application.builder("gbcs")
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
.configurationDirectoryPropertyKey("net.woggioni.gbcs.conf.dir")
.build()
val confDir = app.computeConfigurationDirectory()
val configurationFile = confDir.resolve("gbcs.xml")
if(!Files.exists(configurationFile)) {
Files.createDirectories(confDir)
val defaultConfigurationFileResourcePath = "net/woggioni/gbcs/gbcs-default.xml"
val defaultConfigurationFileResource = GradleBuildCacheServer.javaClass.classLoader
.getResource(defaultConfigurationFileResourcePath)
?: throw IllegalStateException(
"Missing default configuration file 'classpath:$defaultConfigurationFileResourcePath'")
Files.newOutputStream(configurationFile).use { outputStream ->
defaultConfigurationFileResource.openStream().use { inputStream ->
JWO.copy(inputStream, outputStream)
}
}
}
val schemaResource = "net/woggioni/gbcs/gbcs.xsd"
val schemaUrl = URL("classpath:net/woggioni/gbcs/gbcs.xsd")
// val schemaUrl = GradleBuildCacheServer::class.java.classLoader.getResource(schemaResource)
// ?: throw IllegalStateException("Missing configuration schema '$schemaResource'")
val schemaUrl2 = URL(schemaUrl.toString())
val dbf = Xml.newDocumentBuilderFactory()
dbf.schema = Xml.getSchema(schemaUrl)
val doc = Files.newInputStream(configurationFile)
.use(dbf.newDocumentBuilder()::parse)
GradleBuildCacheServer(Configuration.parse(doc.documentElement)).run()
}
fun digest(data : ByteArray,