init
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import com.bmuschko.gradle.docker.tasks.AbstractDockerRemoteApiTask
|
||||
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
|
||||
import com.bmuschko.gradle.docker.tasks.image.DockerPushImage
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlinx.serialization)
|
||||
alias(libs.plugins.johnrengelman.shadow)
|
||||
alias(libs.plugins.bmuschko.docker)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
linuxX64 {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "pw.binom.main"
|
||||
}
|
||||
}
|
||||
}
|
||||
mingwX64 {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "pw.binom.main"
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.binom.io.strong.properties.ini)
|
||||
implementation(libs.binom.io.strong.properties.yaml)
|
||||
implementation(libs.binom.io.strong.application)
|
||||
implementation(libs.binom.io.sqlite)
|
||||
implementation(libs.binom.io.http.server.core)
|
||||
implementation(libs.binom.io.http.client)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
api(kotlin("test-common"))
|
||||
api(kotlin("test-annotations-common"))
|
||||
}
|
||||
jvmTest.dependencies {
|
||||
api(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
}
|
||||
val dockerImage = System.getenv("DOCKER_IMAGE_NAME")
|
||||
val dockerLogin = System.getenv("DOCKER_REGISTRY_USERNAME")
|
||||
val dockerPassword = System.getenv("DOCKER_REGISTRY_PASSWORD")
|
||||
val dockerHost = System.getenv("DOCKER_REGISTRY_HOST")
|
||||
|
||||
docker {
|
||||
registryCredentials {
|
||||
url.set(dockerHost)
|
||||
if (dockerLogin != null) {
|
||||
username.set(dockerLogin)
|
||||
}
|
||||
if (dockerPassword != null) {
|
||||
password.set(dockerPassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
val jvmJar by tasks.getting(Jar::class)
|
||||
|
||||
val shadowJar by tasks.register("shadowJar", com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar::class) {
|
||||
from(jvmJar.archiveFile)
|
||||
group = "build"
|
||||
configurations = listOf(project.configurations["jvmRuntimeClasspath"])
|
||||
exclude(
|
||||
"META-INF/*.SF",
|
||||
"META-INF/*.DSA",
|
||||
"META-INF/*.RSA",
|
||||
"META-INF/*.txt",
|
||||
"META-INF/NOTICE",
|
||||
"LICENSE",
|
||||
)
|
||||
manifest {
|
||||
attributes("Main-Class" to "pw.binom.JvmMain")
|
||||
}
|
||||
archiveFileName.set("full-application.jar")
|
||||
}
|
||||
|
||||
val buildImage by tasks.register("buildDockerImage", DockerBuildImage::class) {
|
||||
group = "docker"
|
||||
dependsOn(shadowJar)
|
||||
inputDir.set(projectDir)
|
||||
if (dockerImage != null) {
|
||||
images.add(dockerImage)
|
||||
buildArgs.put("--output", "type=oci,name=$dockerImage")
|
||||
}
|
||||
applyUrl()
|
||||
doFirst {
|
||||
dockerImage ?: throw IllegalArgumentException("DOCKER_IMAGE_NAME is not set")
|
||||
println("Image name: \"$dockerImage\"")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("pushDockerImage", DockerPushImage::class) {
|
||||
group = "docker"
|
||||
applyUrl()
|
||||
dependsOn(buildImage)
|
||||
images.addAll(buildImage.images)
|
||||
// buildImage.images
|
||||
}
|
||||
|
||||
fun AbstractDockerRemoteApiTask.applyUrl() {
|
||||
val host = System.getenv("DOCKER_HOST")
|
||||
if (host != null) {
|
||||
url.set(host)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package pw.binom
|
||||
|
||||
import pw.binom.io.http.Headers
|
||||
|
||||
interface DataLogger {
|
||||
suspend fun save(
|
||||
method: String,
|
||||
request: String,
|
||||
incomeHeader: Headers,
|
||||
incomeData: ByteArray,
|
||||
responseCode: Int,
|
||||
outcomeHeader: Headers,
|
||||
outcomeData: ByteArray,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
@file:JvmName("JvmMain")
|
||||
|
||||
package pw.binom
|
||||
|
||||
import pw.binom.config.DefaultConfig
|
||||
import pw.binom.strong.StrongApplication
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
StrongApplication.run(args) {
|
||||
+DefaultConfig(properties, networkManager)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package pw.binom.config
|
||||
|
||||
import pw.binom.http.client.HttpClientRunnable
|
||||
import pw.binom.http.client.factory.Https11ConnectionFactory
|
||||
import pw.binom.http.client.factory.NativeNetChannelFactory
|
||||
import pw.binom.network.NetworkManager
|
||||
import pw.binom.services.ConsoleDataLogger
|
||||
import pw.binom.services.HttpServer
|
||||
import pw.binom.services.SQLiteDataLogger
|
||||
import pw.binom.services.SpyHandler
|
||||
import pw.binom.strong.Strong
|
||||
import pw.binom.strong.bean
|
||||
import pw.binom.strong.beanAsyncCloseable
|
||||
import pw.binom.strong.properties.StrongProperties
|
||||
|
||||
fun DefaultConfig(config: StrongProperties, networkManager: NetworkManager) = Strong.config {
|
||||
it.beanAsyncCloseable {
|
||||
HttpClientRunnable(
|
||||
idleCoroutineContext = networkManager,
|
||||
factory = Https11ConnectionFactory(),
|
||||
source = NativeNetChannelFactory(networkManager)
|
||||
)
|
||||
}
|
||||
it.bean { HttpServer() }
|
||||
it.bean { SpyHandler() }
|
||||
it.bean { ConsoleDataLogger() }
|
||||
it.bean { SQLiteDataLogger() }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package pw.binom.properties
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import pw.binom.properties.serialization.annotations.PropertiesPrefix
|
||||
|
||||
@PropertiesPrefix("app")
|
||||
@Serializable
|
||||
data class ApplicationProperties(
|
||||
val port: Int,
|
||||
val forward: String,
|
||||
val database: String,
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
package pw.binom.services
|
||||
|
||||
import pw.binom.DataLogger
|
||||
import pw.binom.io.http.Headers
|
||||
import pw.binom.io.http.forEachHeader
|
||||
import pw.binom.logger.Logger
|
||||
import pw.binom.logger.info
|
||||
|
||||
class ConsoleDataLogger : DataLogger {
|
||||
private val logger by Logger.ofThisOrGlobal
|
||||
override suspend fun save(
|
||||
method: String,
|
||||
request: String,
|
||||
incomeHeader: Headers,
|
||||
incomeData: ByteArray,
|
||||
responseCode: Int,
|
||||
outcomeHeader: Headers,
|
||||
outcomeData: ByteArray,
|
||||
) {
|
||||
logger.info("Income: $method $request")
|
||||
logger.info("Request headers:")
|
||||
incomeHeader.forEachHeader { key, value ->
|
||||
logger.info(" $key: $value")
|
||||
}
|
||||
logger.info("Request data [${incomeData.size} bytes]:")
|
||||
logger.info(incomeData.decodeToString())
|
||||
logger.info("Response code: $responseCode")
|
||||
logger.info("Response headers:")
|
||||
outcomeHeader.forEachHeader { key, value ->
|
||||
logger.info(" $key: $value")
|
||||
}
|
||||
logger.info("Request data [${outcomeData.size} bytes]:")
|
||||
logger.info(outcomeData.decodeToString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package pw.binom.services
|
||||
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import pw.binom.DEFAULT_BUFFER_SIZE
|
||||
import pw.binom.io.ByteBufferFactory
|
||||
import pw.binom.io.httpServer.HttpHandler
|
||||
import pw.binom.io.httpServer.HttpServer2
|
||||
import pw.binom.io.httpServer.ListenJob
|
||||
import pw.binom.io.socket.InetSocketAddress
|
||||
import pw.binom.network.NetworkManager
|
||||
import pw.binom.pool.GenericObjectPool
|
||||
import pw.binom.properties.ApplicationProperties
|
||||
import pw.binom.strong.BeanLifeCycle
|
||||
import pw.binom.strong.inject
|
||||
import pw.binom.strong.properties.injectProperty
|
||||
|
||||
class HttpServer {
|
||||
private val httpHandler: HttpHandler by inject()
|
||||
private val networkManager: NetworkManager by inject()
|
||||
private val applicationProperties: ApplicationProperties by injectProperty()
|
||||
private val bufferPool by lazy { GenericObjectPool(ByteBufferFactory(DEFAULT_BUFFER_SIZE)) }
|
||||
|
||||
private var server: HttpServer2? = null
|
||||
private var listenJob: ListenJob? = null
|
||||
|
||||
init {
|
||||
BeanLifeCycle.postConstruct {
|
||||
val server = HttpServer2(handler = httpHandler, dispatcher = networkManager, byteBufferPool = bufferPool)
|
||||
this.server = server
|
||||
listenJob = server.listen(InetSocketAddress.resolve("0.0.0.0", applicationProperties.port))
|
||||
}
|
||||
BeanLifeCycle.preDestroy {
|
||||
listenJob?.cancelAndJoin()
|
||||
server?.asyncCloseAnyway()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package pw.binom.services
|
||||
|
||||
import pw.binom.DataLogger
|
||||
import pw.binom.db.async.AsyncPreparedStatement
|
||||
import pw.binom.db.sqlite.AsyncConnectionAdapter
|
||||
import pw.binom.db.sqlite.AsyncSQLiteConnector
|
||||
import pw.binom.io.file.File
|
||||
import pw.binom.io.http.Headers
|
||||
import pw.binom.properties.ApplicationProperties
|
||||
import pw.binom.strong.BeanLifeCycle
|
||||
import pw.binom.strong.properties.injectProperty
|
||||
|
||||
class SQLiteDataLogger : DataLogger {
|
||||
private val applicationProperties: ApplicationProperties by injectProperty()
|
||||
private var connection: AsyncConnectionAdapter? = null
|
||||
private var statement: AsyncPreparedStatement? = null
|
||||
|
||||
private fun Headers.makeString() =
|
||||
asSequence().map { (key, list) ->
|
||||
list.asSequence().map { "$key: $it" }
|
||||
}.flatten().joinToString("\n")
|
||||
|
||||
override suspend fun save(
|
||||
method: String,
|
||||
request: String,
|
||||
incomeHeader: Headers,
|
||||
incomeData: ByteArray,
|
||||
responseCode: Int,
|
||||
outcomeHeader: Headers,
|
||||
outcomeData: ByteArray,
|
||||
) {
|
||||
val statement = statement!!
|
||||
statement.set(0, method)
|
||||
statement.set(1, request)
|
||||
statement.set(2, incomeHeader.makeString())
|
||||
statement.set(3, incomeData)
|
||||
statement.set(4, responseCode)
|
||||
statement.set(5, outcomeHeader.makeString())
|
||||
statement.set(6, outcomeData)
|
||||
statement.executeUpdate()
|
||||
}
|
||||
|
||||
init {
|
||||
BeanLifeCycle.postConstruct {
|
||||
val connection = AsyncSQLiteConnector.openFile(File(applicationProperties.database))
|
||||
this.connection = connection
|
||||
connection.executeUpdate(
|
||||
"""
|
||||
create table if not exists requests(
|
||||
id integer primary key autoincrement,
|
||||
method text not null,
|
||||
request text not null,
|
||||
income_header text not null,
|
||||
income_data blob not null,
|
||||
response_code int not null,
|
||||
outcome_header text not null,
|
||||
outcome_data blob not null
|
||||
);
|
||||
""".trimIndent()
|
||||
)
|
||||
statement = connection.prepareStatement(
|
||||
"""
|
||||
insert into requests (
|
||||
method,request,income_header,income_data,
|
||||
response_code,outcome_header,outcome_data
|
||||
) values(?,?,?,?,?,?,?)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
BeanLifeCycle.preDestroy {
|
||||
statement?.asyncCloseAnyway()
|
||||
connection?.asyncCloseAnyway()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package pw.binom.services
|
||||
|
||||
import pw.binom.DataLogger
|
||||
import pw.binom.asyncInput
|
||||
import pw.binom.copyTo
|
||||
import pw.binom.http.client.Http11ClientExchange
|
||||
import pw.binom.http.client.HttpClientRunnable
|
||||
import pw.binom.io.ByteArrayOutput
|
||||
import pw.binom.io.http.headersOf
|
||||
import pw.binom.io.httpServer.HttpHandler
|
||||
import pw.binom.io.httpServer.HttpServerExchange
|
||||
import pw.binom.io.useAsync
|
||||
import pw.binom.logger.Logger
|
||||
import pw.binom.logger.info
|
||||
import pw.binom.properties.ApplicationProperties
|
||||
import pw.binom.strong.inject
|
||||
import pw.binom.strong.injectServiceList
|
||||
import pw.binom.strong.properties.injectProperty
|
||||
import pw.binom.url.toURL
|
||||
|
||||
class SpyHandler : HttpHandler {
|
||||
private val applicationProperties: ApplicationProperties by injectProperty()
|
||||
private val client: HttpClientRunnable by inject()
|
||||
private val loggers by injectServiceList<DataLogger>()
|
||||
override suspend fun handle(exchange: HttpServerExchange) {
|
||||
val result = "${applicationProperties.forward}${exchange.requestURI}"
|
||||
|
||||
val request = client.request(
|
||||
method = exchange.requestMethod,
|
||||
url = result.toURL()
|
||||
)
|
||||
request.headers.clear()
|
||||
request.headers.addAll(exchange.requestHeaders)
|
||||
|
||||
val income = ByteArrayOutput()
|
||||
val outcome = ByteArrayOutput()
|
||||
var responseCode = 0
|
||||
var outcomeHeader = headersOf()
|
||||
var outcomeData = byteArrayOf()
|
||||
println("exchange.input.available->${exchange.input.available}")
|
||||
exchange.input.useAsync { input ->
|
||||
input.copyTo(income)
|
||||
}
|
||||
val incomeData = income.toByteArray()
|
||||
val r = request.connect() as Http11ClientExchange
|
||||
r.useAsync { response ->
|
||||
response.getOutput().useAsync { output ->
|
||||
outcome.writeFully(incomeData)
|
||||
}
|
||||
|
||||
outcomeHeader = response.getResponseHeaders()
|
||||
responseCode = response.getResponseCode()
|
||||
exchange.startResponse(response.getResponseCode(), outcomeHeader)
|
||||
response.getInput().useAsync { input ->
|
||||
input.copyTo(outcome)
|
||||
}
|
||||
outcomeData = outcome.toByteArray()
|
||||
exchange.output.useAsync { output ->
|
||||
output.writeFully(outcomeData)
|
||||
}
|
||||
}
|
||||
loggers.forEach { l ->
|
||||
l.save(
|
||||
method = exchange.requestMethod,
|
||||
request = exchange.requestURI.toString(),
|
||||
incomeHeader = exchange.requestHeaders,
|
||||
incomeData = incomeData,
|
||||
responseCode = responseCode,
|
||||
outcomeHeader = outcomeHeader,
|
||||
outcomeData = outcomeData,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user