init commit (true)
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
#Wed Jul 22 21:44:56 MSK 2026
|
||||
gradle.version=9.5.1
|
||||
Binary file not shown.
Binary file not shown.
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="client-jvm-1.0.0-SNAPSHOT">
|
||||
<output-path>$PROJECT_DIR$/client/build/libs</output-path>
|
||||
<root id="archive" name="client-jvm-1.0.0-SNAPSHOT.jar" />
|
||||
</artifact>
|
||||
</component>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="core-jvm-1.0.0-SNAPSHOT">
|
||||
<output-path>$PROJECT_DIR$/core/build/libs</output-path>
|
||||
<root id="archive" name="core-jvm-1.0.0-SNAPSHOT.jar" />
|
||||
</artifact>
|
||||
</component>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/client" />
|
||||
<option value="$PROJECT_DIR$/core" />
|
||||
<option value="$PROJECT_DIR$/server" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.4.0" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="corretto-25" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -13,28 +13,6 @@ Here's a list of features included in this project:
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| [kotlinx.serialization](https://start.ktor.io/p/io.ktor/server-kotlinx-serialization) | Handles JSON serialization using kotlinx.serialization library |
|
||||
| [Content Negotiation](https://start.ktor.io/p/io.ktor/server-content-negotiation) | Provides automatic content conversion according to Content-Type and Accept headers |
|
||||
| [gRPC](https://start.ktor.io/p/org.jetbrains/kotlinx-rpc-grpc) | Adds gRPC services to your Ktor server with kotlinx.rpc |
|
||||
| [Rate Limiting](https://start.ktor.io/p/io.github.flaxoos/server-rate-limiting) | Manage request rate limiting as you see fit |
|
||||
| [Kafka](https://start.ktor.io/p/io.github.flaxoos/server-kafka) | Adds Kafka support to your application |
|
||||
| [Exposed](https://start.ktor.io/p/org.jetbrains/server-exposed) | Adds Exposed database to your application |
|
||||
| [Dependency Injection](https://start.ktor.io/p/io.ktor/server-di) | Enables dependency injection for your server |
|
||||
| [OpenTelemetry](https://start.ktor.io/p/io.opentelemetry.instrumentation/server-open-telemetry) | Instruments applications with distributed tracing, metrics, and logging for comprehensive observability |
|
||||
| [Sessions](https://start.ktor.io/p/io.ktor/server-sessions) | Adds support for persistent sessions through cookies or headers |
|
||||
| [Authentication](https://start.ktor.io/p/io.ktor/server-auth) | Provides extension point for handling the Authorization header |
|
||||
| [Authentication Basic](https://start.ktor.io/p/io.ktor/server-auth-basic) | Handles 'Basic' username / password authentication scheme |
|
||||
| [Swagger](https://start.ktor.io/p/io.ktor/server-swagger) | Serves Swagger UI for your project |
|
||||
|
||||
## Structure
|
||||
This project includes the following modules:
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| | null |
|
||||
| client | null |
|
||||
| core | null |
|
||||
| server | null |
|
||||
|
||||
## Building & Running
|
||||
To build or run the project, use one of the following tasks:
|
||||
@@ -42,9 +20,9 @@ To build or run the project, use one of the following tasks:
|
||||
|
||||
| Task | Description |
|
||||
|------|-------------|
|
||||
| `./gradlew :server:test` | Run the tests |
|
||||
| `./gradlew :server:build` | Build the project |
|
||||
| `./gradlew :server:run` | Run the server |
|
||||
| `./gradlew test` | Run the tests |
|
||||
| `./gradlew build` | Build the project |
|
||||
| `./gradlew run` | Run the server |
|
||||
|
||||
If the server starts successfully, you'll see the following output:
|
||||
```
|
||||
|
||||
+20
-7
@@ -1,12 +1,25 @@
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform) apply false
|
||||
alias(libs.plugins.kotlin.jvm) apply false
|
||||
alias(libs.plugins.kotlin.serialization) apply false
|
||||
alias(libs.plugins.kotlinx.rpc.grpc) apply false
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(ktorLibs.plugins.ktor)
|
||||
}
|
||||
|
||||
subprojects {
|
||||
group = "tech.aklivtsov"
|
||||
version = "1.0.0-SNAPSHOT"
|
||||
group = "com.example"
|
||||
version = "1.0.0-SNAPSHOT"
|
||||
|
||||
application {
|
||||
mainClass = "io.ktor.server.netty.EngineMain"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
}
|
||||
dependencies {
|
||||
implementation(ktorLibs.server.config.yaml)
|
||||
implementation(ktorLibs.server.core)
|
||||
implementation(ktorLibs.server.netty)
|
||||
implementation(libs.logback.classic)
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation(ktorLibs.server.testHost)
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlinx.rpc.grpc)
|
||||
}
|
||||
|
||||
|
||||
rpc {
|
||||
protoc()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(ktorLibs.client.core)
|
||||
implementation(libs.kotlinx.rpc.grpc.client)
|
||||
implementation(project(":core"))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import kotlinx.rpc.grpc.client.GrpcClient
|
||||
import kotlinx.rpc.withService
|
||||
|
||||
suspend fun main() {
|
||||
val client = GrpcClient("localhost", GRPC_PORT) {
|
||||
credentials = plaintext()
|
||||
}
|
||||
|
||||
val service = client.withService<SampleService>()
|
||||
|
||||
val response = service.greeting(
|
||||
ClientGreeting {
|
||||
name = "World"
|
||||
}
|
||||
)
|
||||
println("Response: ${response.content}")
|
||||
|
||||
client.shutdown()
|
||||
client.awaitTermination()
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.*
|
||||
import io.ktor.http.*
|
||||
import io.opentelemetry.instrumentation.ktor.v3_0.KtorClientTelemetry
|
||||
|
||||
val httpClient = HttpClient {
|
||||
val openTelemetry = getOpenTelemetry(serviceName = "opentelemetry-ktor-sample-client")
|
||||
|
||||
install(KtorClientTelemetry) {
|
||||
setOpenTelemetry(openTelemetry)
|
||||
|
||||
capturedRequestHeaders(HttpHeaders.Accept)
|
||||
|
||||
attributesExtractor {
|
||||
onStart {
|
||||
attributes.put("start-time", System.currentTimeMillis())
|
||||
}
|
||||
onEnd {
|
||||
attributes.put("end-time", System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlinx.rpc.grpc)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
|
||||
rpc {
|
||||
protoc()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
api(libs.opentelemetry.exporterOtlp)
|
||||
api(libs.opentelemetry.ktorInstrumentation)
|
||||
api(libs.opentelemetry.sdkAutoconfigure)
|
||||
api(libs.opentelemetry.semconv)
|
||||
implementation(libs.kotlinx.rpc.grpc.core)
|
||||
implementation(libs.kotlinx.rpc.protobufCore)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.opentelemetry.api.OpenTelemetry
|
||||
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk
|
||||
import io.opentelemetry.semconv.ServiceAttributes
|
||||
|
||||
fun getOpenTelemetry(serviceName: String): OpenTelemetry {
|
||||
System.setProperty("otel.metrics.exporter", "none")
|
||||
return AutoConfiguredOpenTelemetrySdk.builder().addResourceCustomizer { oldResource, _ ->
|
||||
oldResource.toBuilder()
|
||||
.putAll(oldResource.attributes)
|
||||
.put(ServiceAttributes.SERVICE_NAME, serviceName)
|
||||
.build()
|
||||
}.build().openTelemetrySdk
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
const val GRPC_PORT = 8081
|
||||
@@ -1,15 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package tech.aklivtsov;
|
||||
|
||||
message ClientGreeting {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message ServerGreeting {
|
||||
string content = 2;
|
||||
}
|
||||
|
||||
service SampleService {
|
||||
rpc greeting(ClientGreeting) returns (ServerGreeting);
|
||||
}
|
||||
@@ -1,36 +1,10 @@
|
||||
[versions]
|
||||
exposed = "1.3.0"
|
||||
flaxoos = "2.3.0"
|
||||
grpc-netty = "1.82.1"
|
||||
h2 = "2.4.240"
|
||||
h2-r2dbc = "1.1.0.RELEASE"
|
||||
kotlin = "2.4.0"
|
||||
kotlinx-rpc-grpc = "0.11.0-grpc-188"
|
||||
logback = "1.5.35"
|
||||
opentelemetry = "1.63.0"
|
||||
opentelemetry-ktor = "2.29.0-alpha"
|
||||
opentelemetry-semconv = "1.42.0"
|
||||
|
||||
[libraries]
|
||||
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
|
||||
exposed-r2dbc = { module = "org.jetbrains.exposed:exposed-r2dbc", version.ref = "exposed" }
|
||||
flaxoos-ktor-server-kafka = { module = "io.github.flaxoos:ktor-server-kafka", version.ref = "flaxoos" }
|
||||
flaxoos-ktor-server-rateLimiting = { module = "io.github.flaxoos:ktor-server-rate-limiting", version.ref = "flaxoos" }
|
||||
grpc-netty = { module = "io.grpc:grpc-netty", version.ref = "grpc-netty" }
|
||||
h2database-h2 = { module = "com.h2database:h2", version.ref = "h2" }
|
||||
h2database-r2dbc = { module = "io.r2dbc:r2dbc-h2", version.ref = "h2-r2dbc" }
|
||||
kotlinx-rpc-grpc-client = { module = "org.jetbrains.kotlinx:kotlinx-rpc-grpc-client", version.ref = "kotlinx-rpc-grpc" }
|
||||
kotlinx-rpc-grpc-core = { module = "org.jetbrains.kotlinx:kotlinx-rpc-grpc-core", version.ref = "kotlinx-rpc-grpc" }
|
||||
kotlinx-rpc-grpc-ktorServer = { module = "org.jetbrains.kotlinx:kotlinx-rpc-grpc-ktor-server", version.ref = "kotlinx-rpc-grpc" }
|
||||
kotlinx-rpc-protobufCore = { module = "org.jetbrains.kotlinx:kotlinx-rpc-protobuf-core", version.ref = "kotlinx-rpc-grpc" }
|
||||
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||
opentelemetry-exporterOtlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
|
||||
opentelemetry-ktorInstrumentation = { module = "io.opentelemetry.instrumentation:opentelemetry-ktor-3.0", version.ref = "opentelemetry-ktor" }
|
||||
opentelemetry-sdkAutoconfigure = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure", version.ref = "opentelemetry" }
|
||||
opentelemetry-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version.ref = "opentelemetry-semconv" }
|
||||
|
||||
[plugins]
|
||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
kotlinx-rpc-grpc = { id = "org.jetbrains.kotlinx.rpc.plugin", version.ref = "kotlinx-rpc-grpc" }
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(ktorLibs.plugins.ktor)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.kotlinx.rpc.grpc)
|
||||
}
|
||||
|
||||
|
||||
application {
|
||||
mainClass = "io.ktor.server.netty.EngineMain"
|
||||
}
|
||||
rpc {
|
||||
protoc()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
}
|
||||
dependencies {
|
||||
implementation(ktorLibs.client.core)
|
||||
implementation(ktorLibs.serialization.kotlinx.json)
|
||||
implementation(ktorLibs.server.auth)
|
||||
implementation(ktorLibs.server.config.yaml)
|
||||
implementation(ktorLibs.server.contentNegotiation)
|
||||
implementation(ktorLibs.server.core)
|
||||
implementation(ktorLibs.server.di)
|
||||
implementation(ktorLibs.server.netty)
|
||||
implementation(ktorLibs.server.routingOpenapi)
|
||||
implementation(ktorLibs.server.sessions)
|
||||
implementation(ktorLibs.server.swagger)
|
||||
implementation(libs.exposed.core)
|
||||
implementation(libs.exposed.r2dbc)
|
||||
implementation(libs.flaxoos.ktor.server.kafka)
|
||||
implementation(libs.flaxoos.ktor.server.rateLimiting)
|
||||
implementation(libs.grpc.netty)
|
||||
implementation(libs.h2database.h2)
|
||||
implementation(libs.h2database.r2dbc)
|
||||
implementation(libs.kotlinx.rpc.grpc.ktorServer)
|
||||
implementation(libs.logback.classic)
|
||||
implementation(project(":core"))
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation(ktorLibs.server.testHost)
|
||||
testImplementation(libs.kotlinx.rpc.grpc.client)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.di.*
|
||||
|
||||
// The contents of the `install` function will be used for the project template
|
||||
fun Application.configureDependencyInjection() {
|
||||
dependencies {
|
||||
provide { GreetingService { "Hello, World!" } }
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.Kafka
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.MessageTimestampType
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.TopicName
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.admin
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.common
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.consumer
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.consumerConfig
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.consumerRecordHandler
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.producer
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.registerSchemas
|
||||
import io.github.flaxoos.ktor.server.plugins.kafka.topic
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.install
|
||||
|
||||
fun Application.configureDatabases() {
|
||||
install(Kafka) {
|
||||
schemaRegistryUrl = "my.schemaRegistryUrl"
|
||||
val myTopic = TopicName.named("my-topic")
|
||||
topic(myTopic) {
|
||||
partitions = 1
|
||||
replicas = 1
|
||||
configs {
|
||||
messageTimestampType = MessageTimestampType.CreateTime
|
||||
}
|
||||
}
|
||||
common { // <-- Define common properties
|
||||
bootstrapServers = listOf("my-kafka")
|
||||
retries = 1
|
||||
clientId = "my-client-id"
|
||||
}
|
||||
admin { } // <-- Creates an admin client
|
||||
producer { // <-- Creates a producer
|
||||
clientId = "my-client-id"
|
||||
}
|
||||
consumer { // <-- Creates a consumer
|
||||
groupId = "my-group-id"
|
||||
clientId = "my-client-id-override" //<-- Override common properties
|
||||
}
|
||||
consumerConfig {
|
||||
consumerRecordHandler(myTopic) { record ->
|
||||
// Do something with record
|
||||
}
|
||||
}
|
||||
registerSchemas {
|
||||
using { // <-- optionally provide a client, by default CIO is used
|
||||
HttpClient()
|
||||
}
|
||||
// MyRecord::class at myTopic // <-- Will register schema upon startup
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.delete
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.post
|
||||
import io.ktor.server.routing.put
|
||||
import io.ktor.server.routing.routing
|
||||
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
|
||||
|
||||
suspend fun Application.configureExposed() {
|
||||
val database = R2dbcDatabase.connect(
|
||||
url = "r2dbc:h2:file:///./h2",
|
||||
user = "root",
|
||||
password = "",
|
||||
)
|
||||
val userService = ExposedUserService(database).also {
|
||||
it.createSchema()
|
||||
}
|
||||
|
||||
routing {
|
||||
// Create user
|
||||
post("/users") {
|
||||
val user = call.receive<ExposedUser>()
|
||||
val id = userService.create(user)
|
||||
call.respond(HttpStatusCode.Created, id)
|
||||
}
|
||||
|
||||
// Read user
|
||||
get("/users/{id}") {
|
||||
val id = call.parameters["id"]?.toUInt() ?: throw IllegalArgumentException("Invalid ID")
|
||||
val user = userService.read(id)
|
||||
if (user != null) {
|
||||
call.respond(HttpStatusCode.OK, user)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// Update user
|
||||
put("/users/{id}") {
|
||||
val id = call.parameters["id"]?.toUInt() ?: throw IllegalArgumentException("Invalid ID")
|
||||
val user = call.receive<ExposedUser>()
|
||||
userService.update(id, user)
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
|
||||
// Delete user
|
||||
delete("/users/{id}") {
|
||||
val id = call.parameters["id"]?.toUInt() ?: throw IllegalArgumentException("Invalid ID")
|
||||
userService.delete(id)
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
fun interface GreetingService {
|
||||
fun sayHello(): String
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.server.application.Application
|
||||
import kotlinx.rpc.grpc.ktor.server.grpc
|
||||
import kotlinx.rpc.registerService
|
||||
|
||||
fun Application.configureGrpc() {
|
||||
grpc(GRPC_PORT) {
|
||||
services {
|
||||
registerService<SampleService> { SampleServiceImpl() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.swagger.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Application.configureHttp() {
|
||||
routing {
|
||||
swaggerUI(path = "openapi") {
|
||||
/*
|
||||
Documentation source configuration goes here.
|
||||
|
||||
This can be from file (documentation.yaml), or it can be served dynamically from your sources using the
|
||||
`describe {}` API on routes. When `openApi` enabled in Gradle, these calls will be automatically injected
|
||||
based on your code and comments.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MySession(val count: Int = 0)
|
||||
@@ -1,21 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.github.flaxoos.ktor.server.plugins.ratelimiter.*
|
||||
import io.github.flaxoos.ktor.server.plugins.ratelimiter.implementations.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
fun Application.configureRateLimiting() {
|
||||
routing {
|
||||
route("/"){
|
||||
install(RateLimiting) {
|
||||
rateLimiter {
|
||||
type = TokenBucket::class
|
||||
capacity = 100
|
||||
rate = 10.seconds
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import io.ktor.server.auth.*
|
||||
import io.ktor.server.sessions.*
|
||||
|
||||
fun Application.configureRouting() {
|
||||
routing {
|
||||
get("/") {
|
||||
call.respondText("Hello, World!")
|
||||
}
|
||||
authenticate("myauth1") {
|
||||
get("/protected/route/basic") {
|
||||
val principal = call.principal<UserIdPrincipal>()!!
|
||||
call.respondText("Hello ${principal.name}")
|
||||
}
|
||||
}
|
||||
authenticate("myauth2") {
|
||||
get("/protected/route/form") {
|
||||
val principal = call.principal<UserIdPrincipal>()!!
|
||||
call.respondText("Hello ${principal.name}")
|
||||
}
|
||||
}
|
||||
get("/json/kotlinx-serialization") {
|
||||
call.respond(mapOf("hello" to "world"))
|
||||
}
|
||||
get("/session/increment") {
|
||||
val session = call.sessions.get<MySession>() ?: MySession()
|
||||
call.sessions.set(session.copy(count = session.count + 1))
|
||||
call.respondText("Counter is ${session.count}. Refresh to increment.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
class SampleServiceImpl : SampleService {
|
||||
override suspend fun greeting(name: ClientGreeting): ServerGreeting {
|
||||
return ServerGreeting { content = "Hello, ${name.name}!" }
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.sessions.*
|
||||
|
||||
fun Application.configureSecurity() {
|
||||
authentication {
|
||||
basic(name = "myauth1") {
|
||||
realm = "Ktor Server"
|
||||
validate { credentials ->
|
||||
if (credentials.name == credentials.password) {
|
||||
UserIdPrincipal(credentials.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form(name = "myauth2") {
|
||||
userParamName = "user"
|
||||
passwordParamName = "password"
|
||||
challenge {
|
||||
/**/
|
||||
}
|
||||
}
|
||||
}
|
||||
install(Sessions) {
|
||||
cookie<MySession>("MY_SESSION") {
|
||||
cookie.extensions["SameSite"] = "lax"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||
|
||||
fun Application.configureSerialization() {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.application.*
|
||||
import io.opentelemetry.api.trace.SpanKind
|
||||
import io.opentelemetry.instrumentation.ktor.v3_0.KtorServerTelemetry
|
||||
|
||||
fun Application.configureOpenTelemetry() {
|
||||
install(KtorServerTelemetry) {
|
||||
setOpenTelemetry(getOpenTelemetry(serviceName = "ktor-sample"))
|
||||
capturedRequestHeaders(HttpHeaders.UserAgent)
|
||||
spanKindExtractor {
|
||||
if (httpMethod == HttpMethod.Post) {
|
||||
SpanKind.PRODUCER
|
||||
} else {
|
||||
SpanKind.CLIENT
|
||||
}
|
||||
}
|
||||
attributesExtractor {
|
||||
onStart {
|
||||
attributes.put("start-time", System.currentTimeMillis())
|
||||
}
|
||||
onEnd {
|
||||
attributes.put("end-time", System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.singleOrNull
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.core.dao.id.UIntIdTable
|
||||
import org.jetbrains.exposed.v1.r2dbc.*
|
||||
import org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction
|
||||
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
|
||||
|
||||
@Serializable
|
||||
data class ExposedUser(val name: String, val age: Int)
|
||||
|
||||
class ExposedUserService(val database: R2dbcDatabase) {
|
||||
object Users : UIntIdTable() {
|
||||
val name = varchar("name", length = 50)
|
||||
val age = integer("age")
|
||||
}
|
||||
|
||||
suspend fun createSchema() {
|
||||
suspendTransaction(database) {
|
||||
SchemaUtils.create(Users)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun create(user: ExposedUser): UInt = suspendTransaction(database) {
|
||||
val newRecord = Users.insert {
|
||||
it[name] = user.name
|
||||
it[age] = user.age
|
||||
}
|
||||
newRecord[Users.id].value
|
||||
}
|
||||
|
||||
suspend fun read(id: UInt): ExposedUser? {
|
||||
return suspendTransaction(database) {
|
||||
Users.selectAll()
|
||||
.where { Users.id eq id }
|
||||
.map { ExposedUser(it[Users.name], it[Users.age]) }
|
||||
.singleOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(id: UInt, user: ExposedUser) {
|
||||
suspendTransaction(database) {
|
||||
Users.update({ Users.id eq id }) {
|
||||
it[name] = user.name
|
||||
it[age] = user.age
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(id: UInt) {
|
||||
suspendTransaction(database) { Users.deleteWhere { Users.id.eq(id) } }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
ktor:
|
||||
deployment:
|
||||
port: 8080
|
||||
application:
|
||||
modules:
|
||||
- tech.aklivtsov.HttpKt.configureHttp
|
||||
- tech.aklivtsov.SerializationKt.configureSerialization
|
||||
- tech.aklivtsov.DatabasesKt.configureDatabases
|
||||
- tech.aklivtsov.SecurityKt.configureSecurity
|
||||
- tech.aklivtsov.GrpcKt.configureGrpc
|
||||
- tech.aklivtsov.RateLimitingKt.configureRateLimiting
|
||||
- tech.aklivtsov.ExposedKt.configureExposed
|
||||
- tech.aklivtsov.DIKt.configureDependencyInjection
|
||||
- tech.aklivtsov.ServerOpenTelemetryKt.configureOpenTelemetry
|
||||
- tech.aklivtsov.RoutingKt.configureRouting
|
||||
@@ -1,23 +0,0 @@
|
||||
openapi: "3.0.3"
|
||||
info:
|
||||
title: "Application API"
|
||||
description: "Application API"
|
||||
version: "1.0.0"
|
||||
servers:
|
||||
- url: "http://0.0.0.0:8080"
|
||||
paths:
|
||||
/:
|
||||
get:
|
||||
description: "Hello World!"
|
||||
responses:
|
||||
"200":
|
||||
description: "OK"
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: "string"
|
||||
examples:
|
||||
Example#1:
|
||||
value: "Hello World!"
|
||||
components:
|
||||
schemas: {}
|
||||
@@ -1,40 +0,0 @@
|
||||
package tech.aklivtsov
|
||||
|
||||
import io.ktor.server.testing.testApplication
|
||||
import kotlinx.rpc.grpc.client.GrpcClient
|
||||
import kotlinx.rpc.grpc.ktor.server.grpc
|
||||
import kotlinx.rpc.registerService
|
||||
import kotlinx.rpc.withService
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
class ApplicationRpcTest {
|
||||
@Test
|
||||
fun testRpc() = testApplication {
|
||||
application {
|
||||
grpc(8081) {
|
||||
services {
|
||||
registerService<SampleService> { SampleServiceImpl() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startApplication()
|
||||
|
||||
val client = GrpcClient("localhost", 8081) {
|
||||
credentials = plaintext()
|
||||
}
|
||||
|
||||
val response = client.withService<SampleService>().greeting(
|
||||
ClientGreeting {
|
||||
name = "Alex"
|
||||
}
|
||||
)
|
||||
|
||||
assertEquals("Hello, Alex!", response.content, "Wrong response message")
|
||||
|
||||
client.shutdown()
|
||||
client.awaitTermination(1.minutes)
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
maven("https://redirector.kotlinlang.org/maven/kxrpc-grpc")
|
||||
maven("https://packages.confluent.io/maven")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +12,6 @@ plugins {
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://redirector.kotlinlang.org/maven/kxrpc-grpc")
|
||||
maven("https://packages.confluent.io/maven")
|
||||
}
|
||||
versionCatalogs {
|
||||
create("ktorLibs").from("io.ktor:ktor-version-catalog:3.5.0")
|
||||
@@ -24,6 +20,3 @@ dependencyResolutionManagement {
|
||||
|
||||
rootProject.name = "UserService"
|
||||
|
||||
include(":client")
|
||||
include(":core")
|
||||
include(":server")
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Application.configureRouting() {
|
||||
routing {
|
||||
get("/") {
|
||||
call.respondText("Hello, World!")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.aklivtsov
|
||||
package com.example
|
||||
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.application.*
|
||||
@@ -0,0 +1,6 @@
|
||||
ktor:
|
||||
deployment:
|
||||
port: 8080
|
||||
application:
|
||||
modules:
|
||||
- com.example.RoutingKt.configureRouting
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.aklivtsov
|
||||
package com.example
|
||||
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.http.HttpStatusCode
|
||||
Reference in New Issue
Block a user