├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── dependabot.yml └── workflows │ └── detekt-analysis.yml ├── .gitignore ├── LICENSE ├── README.md ├── chat ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── backendMain │ ├── .gitignore │ ├── kotlin │ │ ├── ChatApplication.kt │ │ └── ChatServer.kt │ └── resources │ │ ├── application.conf │ │ ├── logback.xml │ │ └── web │ │ └── index.html │ ├── backendTest │ └── kotlin │ │ └── ChatApplicationTest.kt │ └── frontendMain │ └── kotlin │ └── main.kt ├── client-mpp ├── README.md ├── androidApp │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── io │ │ │ └── ktor │ │ │ └── samples │ │ │ └── mpp │ │ │ └── client │ │ │ └── MainActivity.kt │ │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ └── values │ │ ├── colors.xml │ │ └── styles.xml ├── build.gradle.kts ├── desktopApp │ ├── build.gradle.kts │ └── src │ │ └── nativeMain │ │ └── kotlin │ │ └── DesktopMain.kt ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── iosApp │ ├── iosApp.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcuserdata │ │ │ │ └── rustam.siniukov.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ └── rustam.siniukov.xcuserdatad │ │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── iosApp │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── ContentView.swift │ │ ├── Info.plist │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ └── SceneDelegate.swift │ ├── iosAppTests │ │ ├── Info.plist │ │ └── iosAppTests.swift │ └── iosAppUITests │ │ ├── Info.plist │ │ └── iosAppUITests.swift ├── jsApp │ ├── build.gradle.kts │ └── src │ │ └── jsMain │ │ ├── kotlin │ │ └── JsMain.kt │ │ └── resources │ │ ├── index.html │ │ └── require.min.js ├── settings.gradle.kts └── shared │ ├── build.gradle.kts │ └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── io │ │ └── ktor │ │ └── samples │ │ └── mpp │ │ └── client │ │ └── ApplicationDispatcher.kt │ ├── commonMain │ └── kotlin │ │ └── io │ │ └── ktor │ │ └── samples │ │ └── mpp │ │ └── client │ │ └── Api.kt │ ├── iosMain │ └── kotlin │ │ └── io │ │ └── ktor │ │ └── samples │ │ └── mpp │ │ └── client │ │ └── ApplicationDispatcher.kt │ ├── jsMain │ └── kotlin │ │ └── io.ktor.samples.mpp.client │ │ └── ApplicationDispatcher.kt │ ├── macosArm64Main │ └── kotlin │ │ └── io.ktor │ │ └── samples.mpp.client │ │ └── ApplicationDispatcher.kt │ └── macosX64Main │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── mpp │ └── client │ └── ApplicationDispatcher.kt ├── client-multipart ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── MultipartApp.kt ├── client-native-image ├── .gitignore ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ └── main │ ├── kotlin │ └── App.kt │ └── resources │ └── META-INF │ └── native-image │ └── io.ktor.samples │ └── client │ └── reflect-config.json ├── client-tools ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── ToolsApp.kt ├── di-kodein ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── ktor │ │ └── samples │ │ └── kodein │ │ ├── KodeinAdvancedApplication.kt │ │ └── KodeinSimpleApplication.kt │ └── test │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── kodein │ └── KodeinSimpleApplicationTest.kt ├── filelisting ├── README.md ├── build.gradle.kts ├── files │ ├── a.txt │ ├── b.txt │ └── folder │ │ └── a.txt ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── filelisting │ └── FileListingApplication.kt ├── fullstack-mpp ├── README.md ├── build.gradle.kts ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ ├── backendMain │ ├── kotlin │ │ └── BackendCode.kt │ └── resources │ │ ├── application.conf │ │ └── logback.xml │ ├── commonMain │ └── kotlin │ │ └── CommonCode.kt │ ├── commonTest │ └── kotlin │ │ └── CommonCodeTest.kt │ └── frontendMain │ ├── .gitignore │ ├── kotlin │ └── FrontendCode.kt │ └── resources │ └── require.min.js ├── graalvm ├── README.md ├── build.gradle.kts ├── graal-server ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── io │ │ │ └── ktorgraal │ │ │ └── Application.kt │ └── resources │ │ └── META-INF │ │ └── native-image │ │ └── reflect-config.json │ └── test │ ├── kotlin │ └── io │ │ └── ktorgraal │ │ └── ConfigureRoutingTest.kt │ └── resources │ └── META-INF │ └── native-image │ ├── reflect-config.json │ └── resource-config.json ├── h2 ├── .gitignore ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── io │ │ │ └── ktor │ │ │ └── samples │ │ │ ├── Application.kt │ │ │ ├── H2.kt │ │ │ └── plugins │ │ │ ├── MessageBoard.kt │ │ │ └── Routing.kt │ └── resources │ │ ├── application.conf │ │ └── logback.xml │ └── test │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── ApplicationTest.kt ├── httpbin ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── request.http └── src │ ├── main │ ├── kotlin │ │ └── io │ │ │ └── ktor │ │ │ └── samples │ │ │ └── httpbin │ │ │ ├── Helpers.kt │ │ │ ├── HttpBinApplication.kt │ │ │ ├── HttpBinError.kt │ │ │ └── HttpBinResponse.kt │ └── resources │ │ ├── application.conf │ │ ├── logback.xml │ │ └── static │ │ ├── LICENSE │ │ ├── UTF-8-demo.html │ │ ├── forms-post.html │ │ ├── httpbin.js │ │ ├── httpbin.postman_collection.json │ │ ├── index.html │ │ ├── jackal.jpg │ │ ├── moby.html │ │ ├── pig_icon.png │ │ ├── robots.txt │ │ ├── sample.xml │ │ ├── svg_logo.svg │ │ └── wolf_1.webp │ └── test │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── httpbin │ └── HttpBinApplicationTest.kt ├── jwt-auth-tests ├── README.md ├── build.gradle.kts ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── io │ │ │ └── ktor │ │ │ └── samples │ │ │ └── jwtauth │ │ │ └── Main.kt │ └── resources │ │ ├── application.conf │ │ └── jwks.json │ └── test │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── jwtauth │ └── ApplicationTest.kt ├── ktor-client-wasm ├── .fleet │ └── receipt.json ├── .gitignore ├── README.md ├── build.gradle.kts ├── composeApp │ ├── build.gradle.kts │ └── src │ │ ├── commonMain │ │ └── composeResources │ │ │ └── drawable │ │ │ └── compose-multiplatform.xml │ │ └── wasmJsMain │ │ ├── kotlin │ │ ├── App.kt │ │ ├── Greeting.kt │ │ ├── Platform.kt │ │ └── main.kt │ │ └── resources │ │ ├── index.html │ │ └── kodee.png ├── gradle.properties ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts ├── kweet ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── main │ ├── kotlin │ │ └── io │ │ │ └── ktor │ │ │ └── samples │ │ │ └── kweet │ │ │ ├── Delete.kt │ │ │ ├── Index.kt │ │ │ ├── KweetApplication.kt │ │ │ ├── Login.kt │ │ │ ├── PostNew.kt │ │ │ ├── Register.kt │ │ │ ├── Styles.kt │ │ │ ├── UserPage.kt │ │ │ ├── ViewKweet.kt │ │ │ ├── dao │ │ │ ├── Cache.kt │ │ │ ├── DAOFacadeDatabase.kt │ │ │ ├── Kweets.kt │ │ │ └── Users.kt │ │ │ └── model │ │ │ ├── Kweet.kt │ │ │ └── User.kt │ └── resources │ │ ├── application.conf │ │ ├── blog.css │ │ ├── logback.xml │ │ └── templates │ │ ├── index.ftl │ │ ├── login.ftl │ │ ├── new-kweet.ftl │ │ ├── register.ftl │ │ ├── template.ftl │ │ ├── user.ftl │ │ └── view-kweet.ftl │ └── test │ └── kotlin │ └── KweetApplicationTest.kt ├── location-header ├── README.md ├── Test.http ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── location │ └── LocationHeaderApplication.kt ├── maven-google-appengine-standard ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── resources │ └── application.conf ├── src │ └── HelloApplication.kt └── webapp │ └── WEB-INF │ ├── appengine-web.xml │ ├── logging.properties │ └── web.xml ├── mongodb ├── .gitignore ├── README.md ├── build.gradle.kts ├── docker-compose.yaml ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── example │ │ │ ├── Application.kt │ │ │ ├── entities │ │ │ ├── Article.kt │ │ │ ├── ArticleExtension.kt │ │ │ ├── CreateArticle.kt │ │ │ └── ErrorResponse.kt │ │ │ ├── plugins │ │ │ ├── Routing.kt │ │ │ └── Serialization.kt │ │ │ └── service │ │ │ └── ArticleService.kt │ └── resources │ │ ├── application.yaml │ │ └── logback.xml │ └── test │ └── kotlin │ └── com │ └── example │ └── ApplicationTest.kt ├── mvc-web ├── .gitignore ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── example │ │ ├── Application.kt │ │ ├── controller │ │ └── WishController.kt │ │ ├── exceptions │ │ └── DbElementInsertException.kt │ │ ├── model │ │ ├── DatabaseSingleton.kt │ │ ├── dao │ │ │ └── wishlist │ │ │ │ ├── WishlistDAOFacade.kt │ │ │ │ ├── WishlistDAOFacadeImpl.kt │ │ │ │ └── Wishlists.kt │ │ ├── entity │ │ │ └── Wishlist.kt │ │ └── repository │ │ │ ├── TopWishListExampleRepository.kt │ │ │ └── WishListRepository.kt │ │ └── plugins │ │ ├── Routing.kt │ │ └── Templating.kt │ └── resources │ ├── application.yaml │ ├── logback.xml │ └── view │ ├── topwishes.ftl │ └── wishlist.ftl ├── native-image-server-with-yaml-config ├── .gitignore ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── native-image │ ├── jni-config.json │ ├── predefined-classes-config.json │ ├── proxy-config.json │ ├── reflect-config.json │ ├── resource-config.json │ └── serialization-config.json ├── settings.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── example │ │ │ └── com │ │ │ ├── Application.kt │ │ │ └── plugins │ │ │ ├── Routing.kt │ │ │ └── Serialization.kt │ └── resources │ │ ├── META-INF │ │ └── native-image │ │ │ ├── jni-config.json │ │ │ ├── predefined-classes-config.json │ │ │ ├── proxy-config.json │ │ │ ├── reflect-config.json │ │ │ ├── resource-config.json │ │ │ └── serialization-config.json │ │ ├── application.yaml │ │ └── logback.xml │ └── test │ └── kotlin │ └── example │ └── com │ └── ApplicationTest.kt ├── opentelemetry ├── README.md ├── build.gradle.kts ├── client │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── opentelemetry │ │ └── ktor │ │ └── example │ │ ├── Client.kt │ │ ├── Requests.kt │ │ └── plugins │ │ └── opentelemetry │ │ └── setupClientTelemetry.kt ├── docker │ └── docker-compose.yml ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images │ ├── 1.png │ ├── 2.png │ └── 3.png ├── server │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── opentelemetry │ │ └── ktor │ │ └── example │ │ ├── Server.kt │ │ └── plugins │ │ ├── opentelemetry │ │ └── setupServerTelemetry.kt │ │ └── routing │ │ └── Routing.kt ├── settings.gradle.kts └── shared │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── opentelemetry │ └── ktor │ └── example │ ├── constants.kt │ └── utils.kt ├── postgres ├── .gitignore ├── README.md ├── build.gradle.kts ├── docker-compose.yaml ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── postgres.yaml ├── settings.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── example │ │ ├── Application.kt │ │ ├── exceptions │ │ ├── DbElementInsertException.kt │ │ └── DbElementNotFoundException.kt │ │ ├── models │ │ └── Article.kt │ │ ├── plugins │ │ ├── Routing.kt │ │ └── Serialization.kt │ │ └── service │ │ └── ArticleService.kt │ └── resources │ ├── application.yaml │ └── logback.xml ├── redirect-with-exception ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── redirectwithexception │ └── RedirectWithExceptionApplication.kt ├── renovate.json ├── reverse-proxy-ws ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── reverseproxyws │ └── ReverseProxyWsApplication.kt ├── reverse-proxy ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── reverseproxy │ └── ReverseProxyApplication.kt ├── rx ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── rx │ └── RxApplication.kt ├── sse ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ └── io │ └── ktor │ └── samples │ └── sse │ └── SseApplication.kt ├── structured-logging ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ ├── kotlin │ └── io │ │ └── ktor │ │ └── samples │ │ └── structuredlogging │ │ ├── Application.kt │ │ └── StructuredLogging.kt │ └── resources │ └── logback.xml ├── version-diff ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── Main.kt │ └── Utils.kt └── youkube ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main ├── kotlin │ └── io │ │ └── ktor │ │ └── samples │ │ └── youkube │ │ ├── Database.kt │ │ ├── Login.kt │ │ ├── Page.kt │ │ ├── Styles.kt │ │ ├── Upload.kt │ │ ├── Video.kt │ │ ├── Videos.kt │ │ └── YoukubeApplication.kt └── resources │ ├── application.conf │ ├── blog.css │ └── logback.xml └── test └── kotlin └── YoukubeApplicationTest.kt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Important notice 11 | 12 | We're currently in the process of migrating issues over to [YouTrack](https://youtrack.jetbrains.com/issue/KTOR), which will not allow to provide us with many benefits but also allow better management of issues. We'd really appreciate if you would log your bug directly on YouTrack. If you're not registered, do not worry, as you can log in using your GitHub account. 13 | 14 | [Click here to report a new bug](https://youtrack.jetbrains.com/newIssue?project=KTOR&c=Subsystem%20Docs) 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: maven 8 | directory: "/" 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | target 3 | .gradle 4 | local.properties 5 | *.lock 6 | /app/snippets-api 7 | *.code-workspace 8 | .idea 9 | -------------------------------------------------------------------------------- /chat/README.md: -------------------------------------------------------------------------------- 1 | # Chat 2 | 3 | A chat application written with [Ktor](https://ktor.io) using [WebSockets](https://ktor.io/docs/websocket.html) and [Sessions](https://ktor.io/docs/sessions.html). 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. -------------------------------------------------------------------------------- /chat/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/chat/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /chat/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /chat/src/backendMain/.gitignore: -------------------------------------------------------------------------------- 1 | resources/web/chat.js 2 | resources/web/chat.js.map -------------------------------------------------------------------------------- /chat/src/backendMain/resources/application.conf: -------------------------------------------------------------------------------- 1 | # You can read more about this file: https://ktor.io/docs/configurations.html#configuration-file 2 | ktor { 3 | deployment { 4 | port = 8080 5 | } 6 | 7 | application { 8 | modules = [ io.ktor.samples.chat.backend.ChatApplicationKt.main ] 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /chat/src/backendMain/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client-mpp/README.md: -------------------------------------------------------------------------------- 1 | # Client Multiplatform 2 | A sample project showing how to use a Ktor client in a [multiplatform application](https://ktor.io/docs/getting-started-ktor-client-multiplatform-mobile.html). 3 | 4 | ## Running 5 | An application works on the following platforms: `Android`, `iOS`, `JavaScript`, and `macosArm64`. To run the application, open it in IntelliJ IDEA and do one of the following: 6 | * To run the Android application, use the `client-mpp.androidApp` [run configuration](https://www.jetbrains.com/help/idea/run-debug-configuration.html) created by IntelliJ IDEA automatically. 7 | 8 | * To run the iOS application, open the [iosApp](iosApp) directory in Xcode and run it. 9 | * To run the JavaScript application, execute the following command in a project's root directory: 10 | ``` 11 | ./gradlew :jsApp:run 12 | ``` 13 | * To run `macosArm64`, execute the following command in a project's root directory: 14 | ``` 15 | ./gradlew :desktopApp:runDebugExecutableDesktop 16 | ``` -------------------------------------------------------------------------------- /client-mpp/androidApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | dependencies { 7 | implementation(project(":shared")) 8 | implementation("com.google.android.material:material:1.12.0") 9 | implementation("androidx.appcompat:appcompat:1.7.0") 10 | implementation("androidx.constraintlayout:constraintlayout:2.2.0") 11 | } 12 | 13 | android { 14 | compileSdk = 34 15 | defaultConfig { 16 | applicationId = "io.ktor.samples.mpp.client" 17 | minSdk = 21 18 | targetSdk = 33 19 | versionCode = 1 20 | versionName = "1.0" 21 | } 22 | buildTypes { 23 | release { 24 | isMinifyEnabled = false 25 | } 26 | } 27 | packaging { 28 | resources.excludes.add("META-INF/*.kotlin_module") 29 | } 30 | compileOptions { 31 | sourceCompatibility = JavaVersion.VERSION_17 32 | targetCompatibility = JavaVersion.VERSION_17 33 | } 34 | kotlinOptions { 35 | jvmTarget = "17" 36 | } 37 | namespace = "io.ktor.samples.mpp.client" 38 | } 39 | -------------------------------------------------------------------------------- /client-mpp/androidApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /client-mpp/androidApp/src/main/java/io/ktor/samples/mpp/client/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.mpp.client 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.widget.TextView 6 | 7 | 8 | class MainActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_main) 12 | 13 | val tv: TextView = findViewById(R.id.text_view) 14 | ApplicationApi().about { tv.text = it } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client-mpp/androidApp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 20 | 21 | -------------------------------------------------------------------------------- /client-mpp/androidApp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | -------------------------------------------------------------------------------- /client-mpp/androidApp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /client-mpp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | dependencies { 8 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20") 9 | classpath("com.android.tools.build:gradle:8.6.1") 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } -------------------------------------------------------------------------------- /client-mpp/desktopApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.jetbrains.kotlin.multiplatform") 3 | } 4 | 5 | kotlin { 6 | // on Linux X64 7 | // linuxX64("desktop") 8 | // on Windows x64 9 | // mingwX64("desktop") 10 | // on MacOs X64 11 | // macosX64("desktop") 12 | // on MacOS Arm64 13 | macosArm64("desktop") { 14 | binaries { 15 | executable() 16 | } 17 | } 18 | sourceSets { 19 | nativeMain { 20 | dependencies { 21 | implementation(project(":shared")) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client-mpp/desktopApp/src/nativeMain/kotlin/DesktopMain.kt: -------------------------------------------------------------------------------- 1 | import io.ktor.samples.mpp.client.* 2 | import kotlinx.coroutines.runBlocking 3 | import kotlin.coroutines.resume 4 | import kotlin.coroutines.suspendCoroutine 5 | 6 | fun main() = runBlocking { 7 | val result = suspendCoroutine { continuation -> 8 | ApplicationApi().about { 9 | continuation.resume(it) 10 | } 11 | } 12 | 13 | println("Result: $result") 14 | } -------------------------------------------------------------------------------- /client-mpp/gradle.properties: -------------------------------------------------------------------------------- 1 | #Kotlin 2 | kotlin.code.style=official 3 | kotlin.daemon.jvmargs=-Xmx2048M 4 | 5 | #Gradle 6 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 7 | 8 | #Android 9 | android.nonTransitiveRClass=true 10 | android.useAndroidX=true -------------------------------------------------------------------------------- /client-mpp/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/client-mpp/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /client-mpp/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/rustam.siniukov.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/client-mpp/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/rustam.siniukov.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp.xcodeproj/xcuserdata/rustam.siniukov.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp.xcodeproj/xcuserdata/rustam.siniukov.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | iosApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 7 | // Override point for customization after application launch. 8 | return true 9 | } 10 | 11 | // MARK: UISceneSession Lifecycle 12 | 13 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 14 | // Called when a new scene session is being created. 15 | // Use this method to select a configuration to create the new scene with. 16 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 17 | } 18 | 19 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 20 | // Called when the user discards a scene session. 21 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 22 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import shared 3 | 4 | class ViewModel : ObservableObject{ 5 | @Published var content: String = "loading" 6 | 7 | init() { 8 | load() 9 | } 10 | 11 | func load() -> Void { 12 | ApplicationApi().about { (text) in 13 | self.content = text 14 | } 15 | } 16 | } 17 | 18 | struct ContentView: View { 19 | 20 | @ObservedObject 21 | var viewModel = ViewModel() 22 | 23 | var body: some View { 24 | Text(viewModel.content) 25 | } 26 | } 27 | 28 | struct ContentView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | ContentView() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /client-mpp/iosApp/iosAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosAppTests/iosAppTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import iosApp 3 | 4 | class iosAppTests: XCTestCase { 5 | 6 | override func setUp() { 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | } 13 | 14 | func testExample() { 15 | // This is an example of a functional test case. 16 | // Use XCTAssert and related functions to verify your tests produce the correct results. 17 | } 18 | 19 | func testPerformanceExample() { 20 | // This is an example of a performance test case. 21 | self.measure { 22 | // Put the code you want to measure the time of here. 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /client-mpp/iosApp/iosAppUITests/iosAppUITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | class appNameUITests: XCTestCase { 4 | 5 | override func setUp() { 6 | // Put setup code here. This method is called before the invocation of each test method in the class. 7 | 8 | // In UI tests it is usually best to stop immediately when a failure occurs. 9 | continueAfterFailure = false 10 | 11 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 12 | } 13 | 14 | override func tearDown() { 15 | // Put teardown code here. This method is called after the invocation of each test method in the class. 16 | } 17 | 18 | func testExample() { 19 | // UI tests must launch the application that they test. 20 | let app = XCUIApplication() 21 | app.launch() 22 | 23 | // Use recording to get started writing UI tests. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testLaunchPerformance() { 28 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 29 | // This measures how long it takes to launch your application. 30 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 31 | XCUIApplication().launch() 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client-mpp/jsApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.jetbrains.kotlin.multiplatform") 3 | } 4 | 5 | kotlin { 6 | js(IR) { 7 | browser { 8 | } 9 | binaries.executable() 10 | } 11 | 12 | sourceSets { 13 | val jsMain by getting { 14 | dependencies { 15 | implementation(project(":shared")) 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client-mpp/jsApp/src/jsMain/kotlin/JsMain.kt: -------------------------------------------------------------------------------- 1 | import io.ktor.samples.mpp.client.* 2 | import kotlinx.browser.document 3 | 4 | fun main() { 5 | ApplicationApi().about { 6 | val div = document.createElement("pre") 7 | div.textContent = it 8 | document.body?.appendChild(div) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /client-mpp/jsApp/src/jsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | client-mpp 8 | js sample 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client-mpp/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | rootProject.name = "client-mpp" 10 | 11 | include(":androidApp") 12 | include(":desktopApp") 13 | include(":jsApp") 14 | include(":shared") 15 | -------------------------------------------------------------------------------- /client-mpp/shared/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /client-mpp/shared/src/androidMain/kotlin/io/ktor/samples/mpp/client/ApplicationDispatcher.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.mpp.client 2 | 3 | import kotlinx.coroutines.* 4 | 5 | internal actual val ApplicationDispatcher: CoroutineDispatcher = Dispatchers.Main 6 | -------------------------------------------------------------------------------- /client-mpp/shared/src/commonMain/kotlin/io/ktor/samples/mpp/client/Api.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.mpp.client 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.request.* 5 | import io.ktor.client.statement.* 6 | import io.ktor.http.* 7 | import kotlinx.coroutines.* 8 | 9 | internal expect val ApplicationDispatcher: CoroutineDispatcher 10 | 11 | class ApplicationApi { 12 | private val client = HttpClient() 13 | 14 | private val address = Url("https://cors-test.appspot.com/test") 15 | 16 | fun about(callback: (String) -> Unit) { 17 | GlobalScope.launch(ApplicationDispatcher) { 18 | val result: String = client.get { 19 | url(address.toString()) 20 | }.bodyAsText() 21 | 22 | callback(result) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client-mpp/shared/src/iosMain/kotlin/io/ktor/samples/mpp/client/ApplicationDispatcher.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.mpp.client 2 | 3 | import kotlin.coroutines.* 4 | import kotlinx.coroutines.* 5 | import platform.darwin.* 6 | 7 | internal actual val ApplicationDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue()) 8 | 9 | internal class NsQueueDispatcher( 10 | private val dispatchQueue: dispatch_queue_t 11 | ) : CoroutineDispatcher() { 12 | override fun dispatch(context: CoroutineContext, block: Runnable) { 13 | dispatch_async(dispatchQueue) { 14 | block.run() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client-mpp/shared/src/jsMain/kotlin/io.ktor.samples.mpp.client/ApplicationDispatcher.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.mpp.client 2 | 3 | import kotlinx.coroutines.* 4 | 5 | internal actual val ApplicationDispatcher: CoroutineDispatcher = Dispatchers.Default 6 | -------------------------------------------------------------------------------- /client-mpp/shared/src/macosArm64Main/kotlin/io.ktor/samples.mpp.client/ApplicationDispatcher.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.mpp.client 2 | 3 | import kotlinx.coroutines.* 4 | 5 | internal actual val ApplicationDispatcher: CoroutineDispatcher = Dispatchers.Unconfined 6 | -------------------------------------------------------------------------------- /client-mpp/shared/src/macosX64Main/kotlin/io/ktor/samples/mpp/client/ApplicationDispatcher.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.mpp.client 2 | 3 | import kotlinx.coroutines.* 4 | 5 | internal actual val ApplicationDispatcher: CoroutineDispatcher = Dispatchers.Unconfined -------------------------------------------------------------------------------- /client-multipart/README.md: -------------------------------------------------------------------------------- 1 | # Client Multipart 2 | 3 | A sample project for [Ktor](https://ktor.io) showing how to send [multipart data](https://ktor.io/docs/request.html#upload_file) from the HttpClient. 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | And navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 14 | -------------------------------------------------------------------------------- /client-multipart/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | dependencies { 7 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20" 8 | classpath "io.ktor.plugin:plugin:3.1.3" 9 | } 10 | } 11 | 12 | apply plugin: 'kotlin' 13 | apply plugin: 'application' 14 | apply plugin: 'io.ktor.plugin' 15 | 16 | mainClassName = "io.ktor.samples.clientmultipart.MultipartAppKt" 17 | 18 | sourceSets { 19 | main.kotlin.srcDirs = ['src'] 20 | main.resources.srcDirs = ['resources'] 21 | test.kotlin.srcDirs = ['test'] 22 | test.resources.srcDirs = ['testresources'] 23 | } 24 | 25 | repositories { 26 | mavenCentral() 27 | } 28 | 29 | dependencies { 30 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20' 31 | implementation "io.ktor:ktor-server-html-builder" 32 | implementation 'ch.qos.logback:logback-classic:1.5.12' 33 | implementation 'io.ktor:ktor-server-netty-jvm' 34 | implementation 'io.ktor:ktor-client-cio-jvm' 35 | } 36 | 37 | -------------------------------------------------------------------------------- /client-multipart/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/client-multipart/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /client-multipart/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /client-native-image/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Kotlin ### 20 | .kotlin 21 | 22 | ### Eclipse ### 23 | .apt_generated 24 | .classpath 25 | .factorypath 26 | .project 27 | .settings 28 | .springBeans 29 | .sts4-cache 30 | bin/ 31 | !**/src/main/**/bin/ 32 | !**/src/test/**/bin/ 33 | 34 | ### NetBeans ### 35 | /nbproject/private/ 36 | /nbbuild/ 37 | /dist/ 38 | /nbdist/ 39 | /.nb-gradle/ 40 | 41 | ### VS Code ### 42 | .vscode/ 43 | 44 | ### Mac OS ### 45 | .DS_Store -------------------------------------------------------------------------------- /client-native-image/README.md: -------------------------------------------------------------------------------- 1 | # Client Native Image 2 | 3 | This project is a console application that prints the contents of a web page using a GraalVM native image with a Ktor 4 | Client application. 5 | 6 | ## Running the application 7 | 8 | To run the application, use the following command: 9 | 10 | ```shell 11 | ./gradlew nativeRun 12 | ``` 13 | 14 | ## Building the native image 15 | 16 | To build the native image, use the following command: 17 | 18 | ```shell 19 | ./gradlew nativeCompile 20 | ``` 21 | 22 | To execute the native image, use the following command: 23 | 24 | ```shell 25 | ./build/native/nativeCompile/ktor-client-native-image 26 | ``` 27 | -------------------------------------------------------------------------------- /client-native-image/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.21" 3 | application 4 | id("org.graalvm.buildtools.native") version "0.10.3" 5 | } 6 | 7 | group = "io.ktor.samples" 8 | version = "1.0-SNAPSHOT" 9 | 10 | val mainClassName = "io.ktor.samples.client.AppKt" 11 | application { 12 | mainClass = mainClassName 13 | } 14 | 15 | graalvmNative { 16 | binaries { 17 | named("main") { 18 | imageName = "ktor-client-native-image" 19 | mainClass = mainClassName 20 | buildArgs.add("-O4") 21 | } 22 | named("test") { 23 | buildArgs.add("-O0") 24 | } 25 | all { 26 | buildArgs.add("--verbose") 27 | } 28 | } 29 | 30 | toolchainDetection = true 31 | } 32 | 33 | repositories { 34 | mavenCentral() 35 | } 36 | 37 | dependencies { 38 | implementation("io.ktor:ktor-client-apache:3.1.3") 39 | implementation("io.ktor:ktor-client-cio:3.1.3") 40 | testImplementation(kotlin("test")) 41 | } 42 | 43 | tasks.test { 44 | useJUnitPlatform() 45 | } 46 | kotlin { 47 | jvmToolchain(17) 48 | } -------------------------------------------------------------------------------- /client-native-image/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /client-native-image/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/client-native-image/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /client-native-image/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 28 17:31:57 CET 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /client-native-image/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 3 | } 4 | rootProject.name = "client-native-image" 5 | 6 | -------------------------------------------------------------------------------- /client-native-image/src/main/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.client 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.engine.apache.Apache 5 | import io.ktor.client.engine.cio.CIO 6 | import io.ktor.client.request.get 7 | import io.ktor.client.statement.bodyAsText 8 | 9 | suspend fun main() { 10 | val apacheClient = HttpClient(Apache) 11 | val apacheResponse = apacheClient.get("https://ktor.io").bodyAsText() 12 | println("$apacheClient: ${apacheResponse.take(100)}") 13 | 14 | 15 | val cioClient = HttpClient(CIO) 16 | val cioResponse = cioClient.get("https://ktor.io").bodyAsText() 17 | println("$cioClient: ${cioResponse.take(100)}") 18 | } -------------------------------------------------------------------------------- /client-native-image/src/main/resources/META-INF/native-image/io.ktor.samples/client/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "io.ktor.network.selector.InterestSuspensionsMap", 4 | "fields": [ 5 | { 6 | "name": "readHandlerReference" 7 | }, 8 | { 9 | "name": "writeHandlerReference" 10 | }, 11 | { 12 | "name": "acceptHandlerReference" 13 | }, 14 | { 15 | "name": "connectHandlerReference" 16 | } 17 | ] 18 | } 19 | ] -------------------------------------------------------------------------------- /client-tools/README.md: -------------------------------------------------------------------------------- 1 | # Client Tools 2 | 3 | A sample project for [Ktor](https://ktor.io) showing several useful extension methods not included in Ktor itself. 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Note: for this sample to work, you need to open a server in your localhost at port 8080. -------------------------------------------------------------------------------- /client-tools/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | dependencies { 7 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20" 8 | classpath "io.ktor.plugin:plugin:3.1.3" 9 | } 10 | } 11 | 12 | apply plugin: 'kotlin' 13 | apply plugin: 'application' 14 | apply plugin: 'io.ktor.plugin' 15 | 16 | mainClassName = "ToolsAppKt" 17 | 18 | sourceSets { 19 | main.kotlin.srcDirs = ['src'] 20 | main.resources.srcDirs = ['resources'] 21 | test.kotlin.srcDirs = ['test'] 22 | test.resources.srcDirs = ['testresources'] 23 | } 24 | 25 | repositories { 26 | mavenCentral() 27 | } 28 | 29 | dependencies { 30 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20' 31 | implementation "io.ktor:ktor-server-html-builder" 32 | implementation 'ch.qos.logback:logback-classic:1.5.12' 33 | implementation 'io.ktor:ktor-server-netty-jvm' 34 | implementation 'io.ktor:ktor-client-cio-jvm' 35 | } 36 | 37 | -------------------------------------------------------------------------------- /client-tools/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/client-tools/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /client-tools/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /client-tools/src/ToolsApp.kt: -------------------------------------------------------------------------------- 1 | import io.ktor.client.* 2 | import io.ktor.client.request.* 3 | import io.ktor.client.statement.* 4 | import io.ktor.http.* 5 | import io.ktor.util.cio.* 6 | import io.ktor.utils.io.* 7 | import kotlinx.coroutines.* 8 | import java.io.* 9 | import java.net.* 10 | 11 | fun main() { 12 | runBlocking { 13 | val client = HttpClient { 14 | followRedirects = true 15 | } 16 | client.getAsTempFile("http://127.0.0.1:8080/") { file -> 17 | println(file.readBytes().size) 18 | } 19 | } 20 | } 21 | 22 | data class HttpClientException(val response: HttpResponse) : IOException("HTTP Error ${response.status}") 23 | 24 | suspend fun HttpClient.getAsTempFile(url: String, callback: suspend (file: File) -> Unit) { 25 | val file = getAsTempFile(url) 26 | try { 27 | callback(file) 28 | } finally { 29 | file.delete() 30 | } 31 | } 32 | 33 | suspend fun HttpClient.getAsTempFile(url: String): File { 34 | val file = File.createTempFile("ktor", "http-client") 35 | val response = request { 36 | url(URL(url)) 37 | method = HttpMethod.Get 38 | } 39 | if (!response.status.isSuccess()) { 40 | throw HttpClientException(response) 41 | } 42 | response.bodyAsChannel().copyAndClose(file.writeChannel()) 43 | return file 44 | } 45 | -------------------------------------------------------------------------------- /di-kodein/README.md: -------------------------------------------------------------------------------- 1 | # Kodein-DI 2 | 3 | A sample project for [Ktor](https://ktor.io) showing how to use [Kodein DI](https://kodein.org/Kodein-DI/) with Ktor. 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 14 | -------------------------------------------------------------------------------- /di-kodein/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | kotlin("plugin.serialization") version "2.1.20" 8 | } 9 | 10 | application { 11 | mainClass.set("io.ktor.samples.kodein.KodeinSimpleApplicationKt") 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation("io.ktor:ktor-server-core-jvm") 20 | implementation("io.ktor:ktor-server-netty-jvm") 21 | implementation("io.ktor:ktor-server-resources") 22 | implementation("io.ktor:ktor-server-default-headers") 23 | implementation("io.ktor:ktor-server-html-builder") 24 | implementation("org.kodein.di:kodein-di-jvm:7.17.0") 25 | implementation("ch.qos.logback:logback-classic:$logback_version") 26 | testImplementation("io.ktor:ktor-server-test-host-jvm") 27 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 28 | } -------------------------------------------------------------------------------- /di-kodein/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /di-kodein/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/di-kodein/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /di-kodein/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /di-kodein/src/main/kotlin/io/ktor/samples/kodein/KodeinSimpleApplication.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kodein 2 | 3 | import io.ktor.server.application.* 4 | import io.ktor.server.engine.* 5 | import io.ktor.server.netty.* 6 | import io.ktor.server.response.* 7 | import io.ktor.server.routing.* 8 | import org.kodein.di.DI 9 | import org.kodein.di.bind 10 | import org.kodein.di.instance 11 | import org.kodein.di.singleton 12 | import java.security.SecureRandom 13 | import java.util.* 14 | 15 | /** 16 | * An entry point of the embedded-server program: 17 | * 18 | * io.ktor.samples.kodein.KodeinSimpleApplicationKt.main 19 | * 20 | * This would start and wait a web-server at port 8080 using Netty, 21 | * and would load the 'myKodeinApp' ktor module. 22 | */ 23 | fun main() { 24 | embeddedServer(Netty, port = 8080, module = Application::myKodeinApp).start(wait = true) 25 | } 26 | 27 | /** 28 | * The main and only module of the application. 29 | * This module creates a Kodein container and sets 30 | * maps a Random to a singleton based on SecureRandom. 31 | * And then configures the application. 32 | */ 33 | fun Application.myKodeinApp() = myKodeinApp(DI { 34 | bind() with singleton { SecureRandom() } 35 | }) 36 | 37 | /** 38 | * This is the application module that has a 39 | * preconfigured [kodein] instance as input. 40 | * 41 | * The idea of this method, is that the different modules 42 | * can call this with several configured kodein variants 43 | * and also you can call it from the tests setting mocks 44 | * instead of the default mappings. 45 | */ 46 | fun Application.myKodeinApp(kodein: DI) { 47 | val random by kodein.instance() 48 | 49 | routing { 50 | get("/") { 51 | val range = 0 until 100 52 | call.respondText("Random number in $range: ${random[range]}") 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * Convenience [Random] extension operator method to get a random integral value inside the specified [range]. 59 | */ 60 | private operator fun Random.get(range: IntRange) = range.first + this.nextInt(range.last - range.first) 61 | -------------------------------------------------------------------------------- /di-kodein/src/test/kotlin/io/ktor/samples/kodein/KodeinSimpleApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kodein 2 | 3 | import io.ktor.client.request.* 4 | import io.ktor.client.statement.* 5 | import io.ktor.http.* 6 | import io.ktor.samples.kodein.* 7 | import io.ktor.server.testing.* 8 | import org.junit.* 9 | import org.junit.Assert.* 10 | import org.kodein.di.* 11 | import java.util.* 12 | 13 | /** 14 | * Integration tests for the [myKodeinApp] module from KodeinSimpleApplication. 15 | */ 16 | class KodeinSimpleApplicationTest { 17 | /** 18 | * A test that creates the application with a custom [Kodein] 19 | * that maps to a custom [Random] instance. 20 | * 21 | * This [Random] always returns a constant value '7'. 22 | * We then call the single defined entry point to verify 23 | * that when used the Random generator the result is predictable and constant. 24 | * 25 | * Additionally, we test that the [Random.next] function has been called once. 26 | */ 27 | @Test 28 | fun testProvideFakeRandom() { 29 | val log = arrayListOf() 30 | testApplication { 31 | /** Calls the [myKodeinApp] module with a [Random] class that always return 7. */ 32 | application { 33 | myKodeinApp(DI { 34 | bind() with singleton { 35 | object : Random() { 36 | override fun next(bits: Int): Int = 7.also { log += "Random.next" } 37 | } 38 | } 39 | }) 40 | } 41 | 42 | /** 43 | * Checks that the single route, returns a constant value '7' from the mock, 44 | * and that the [Random.next] has been called just once 45 | */ 46 | val response = client.get("/") 47 | assertEquals("Random number in 0..99: 7", response.bodyAsText()) 48 | assertEquals(listOf("Random.next"), log) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /filelisting/README.md: -------------------------------------------------------------------------------- 1 | # File listing 2 | 3 | A sample project for [Ktor](https://ktor.io) showing how to create a file listing support for [static files](https://ktor.io/docs/serving-static-content.html). 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 14 | -------------------------------------------------------------------------------- /filelisting/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.samples.filelisting.FileListingApplicationKt") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-server-core-jvm") 19 | implementation("io.ktor:ktor-server-netty-jvm") 20 | implementation("io.ktor:ktor-server-default-headers") 21 | implementation("io.ktor:ktor-server-html-builder") 22 | implementation("io.ktor:ktor-server-call-logging") 23 | testImplementation("io.ktor:ktor-server-test-host-jvm") 24 | implementation("ch.qos.logback:logback-classic:$logback_version") 25 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 26 | } -------------------------------------------------------------------------------- /filelisting/files/a.txt: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /filelisting/files/b.txt: -------------------------------------------------------------------------------- 1 | world -------------------------------------------------------------------------------- /filelisting/files/folder/a.txt: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /filelisting/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /filelisting/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/filelisting/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /filelisting/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /fullstack-mpp/README.md: -------------------------------------------------------------------------------- 1 | # Full-stack multiplatform application 2 | 3 | A full-stack sample project for [Ktor](https://ktor.io) running as an embedded application and serving 4 | a static folder with kotlin-js code sharing code with the backend. 5 | 6 | The structure: 7 | * [src/commonMain](src/commonMain) - common code shared between the backend and the frontend 8 | * [src/backendMain](src/backendMain) - Ktor backend including fullstack-common code 9 | * [src/frontendMain](src/frontendMain) - kotlin-js code including fullstack-common code 10 | 11 | ## Running 12 | 13 | Run this project with: 14 | 15 | ``` 16 | ./gradlew run 17 | ``` 18 | 19 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 20 | -------------------------------------------------------------------------------- /fullstack-mpp/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/fullstack-mpp/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /fullstack-mpp/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /fullstack-mpp/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ktor-samples" 2 | 3 | pluginManagement { 4 | repositories { 5 | google() 6 | gradlePluginPortal() 7 | mavenCentral() 8 | } 9 | } 10 | 11 | fun module(group: String, name: String) { 12 | include(name) 13 | project(":$name").projectDir = file("$group/$name") 14 | } 15 | 16 | // --------------------------- 17 | 18 | module("samples", "fullstack-mpp") 19 | -------------------------------------------------------------------------------- /fullstack-mpp/src/backendMain/kotlin/BackendCode.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.fullstack.backend 2 | 3 | import io.ktor.samples.fullstack.common.* 4 | import io.ktor.server.application.* 5 | import io.ktor.server.engine.* 6 | import io.ktor.server.html.* 7 | import io.ktor.server.http.content.* 8 | import io.ktor.server.netty.* 9 | import io.ktor.server.response.* 10 | import io.ktor.server.routing.* 11 | import kotlinx.html.* 12 | import java.io.* 13 | 14 | fun Application.main() { 15 | val currentDir = File(".").absoluteFile 16 | environment.log.info("Current directory: $currentDir") 17 | 18 | routing { 19 | get("/") { 20 | call.respondHtml { 21 | body { 22 | +"Hello ${getCommonWorldString()} from Ktor" 23 | div { 24 | id = "js-response" 25 | +"Loading..." 26 | } 27 | script(src = "/static/output.js") { 28 | } 29 | } 30 | } 31 | } 32 | get("/test") { 33 | call.respond("I am a test response") 34 | } 35 | staticResources(remotePath = "/static", basePackage = null) 36 | } 37 | } 38 | 39 | fun main() { 40 | embeddedServer(Netty, port = 8080) { main() }.start(wait = true) 41 | } -------------------------------------------------------------------------------- /fullstack-mpp/src/backendMain/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | } 5 | 6 | application { 7 | modules = [ io.ktor.samples.fullstack.backend.BackendCodeKt.main ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fullstack-mpp/src/backendMain/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /fullstack-mpp/src/commonMain/kotlin/CommonCode.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.fullstack.common 2 | 3 | fun getCommonWorldString() = "common-world" -------------------------------------------------------------------------------- /fullstack-mpp/src/commonTest/kotlin/CommonCodeTest.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.fullstack.common 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class CommonCodeTest { 7 | @Test 8 | fun testGetCommonWorldString() { 9 | assertEquals("common-world", getCommonWorldString()) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /fullstack-mpp/src/frontendMain/.gitignore: -------------------------------------------------------------------------------- 1 | /web 2 | -------------------------------------------------------------------------------- /fullstack-mpp/src/frontendMain/kotlin/FrontendCode.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.fullstack.frontend 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.engine.js.* 5 | import io.ktor.client.request.* 6 | import io.ktor.http.* 7 | import io.ktor.samples.fullstack.common.* 8 | import kotlinx.browser.* 9 | import kotlinx.coroutines.* 10 | 11 | private val client = HttpClient(Js) 12 | private val scope = MainScope() 13 | private val endpoint = window.location.origin 14 | 15 | @Suppress("unused") 16 | @JsName("helloWorld") 17 | fun helloWorld(salutation: String) { 18 | val message = "$salutation from Kotlin.JS ${getCommonWorldString()}" 19 | document.getElementById("js-response")?.textContent = message 20 | 21 | scope.launch { 22 | delay(3000) 23 | document.getElementById("js-response")?.textContent = "making request..." 24 | delay(3000) 25 | val response = client.get { 26 | url.takeFrom(endpoint); 27 | url.appendPathSegments(listOf("test")) 28 | } 29 | document.getElementById("js-response")?.textContent = """result is: "$response"""" 30 | } 31 | } 32 | 33 | fun main() { 34 | document.addEventListener("DOMContentLoaded", { 35 | helloWorld("Hi!") 36 | }) 37 | } -------------------------------------------------------------------------------- /graalvm/README.md: -------------------------------------------------------------------------------- 1 | [![official JetBrains project](https://jb.gg/badges/official-flat-square.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) 2 | 3 | # GraalVM sample for Ktor Server 4 | 5 | A demo project that shows how to combine Ktor server applications with [GraalVM](https://ktor.io/docs/graalvm.html). 6 | 7 | ## Steps 8 | 9 | 1. Make sure that you have [GraalVM](https://graalvm.org) installed and `$GRAALVM_HOME` environment 10 | variable points to the folder where GraalVM is installed, or alternatively that `native-image` is on your path (if on 11 | Windows). 12 | 13 | 2. Run the command `./gradlew nativeCompile` (or `gradlew nativeCompile` on Windows) to build an executable file. 14 | 15 | 3. The previous step produces an executable file named `graal-server` which can then be run. Open up 16 | `http://0.0.0.0:8080` to test the server. 17 | 18 | 4. Run the command `./gradlew nativeTestCompile` (or `gradlew nativeTestCompile` on Windows) to build an executable file for tests. 19 | 20 | 5. That step produces an executable file named `graal-test-server` which can then be run. 21 | 22 | ### Current limitations 23 | 24 | Using the `Netty` engine is not compatible with GraalVM. Please following 25 | the [corresponding issue](https://youtrack.jetbrains.com/issue/KTOR-2558) for 26 | updates. 27 | 28 | ## License 29 | 30 | This sample is provided as is under the Apache 2 OSS license. 31 | 32 | -------------------------------------------------------------------------------- /graalvm/graal-server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/graalvm/graal-server -------------------------------------------------------------------------------- /graalvm/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/graalvm/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /graalvm/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /graalvm/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "graalvm" -------------------------------------------------------------------------------- /graalvm/src/main/kotlin/io/ktorgraal/Application.kt: -------------------------------------------------------------------------------- 1 | package io.ktorgraal 2 | 3 | import io.ktor.serialization.kotlinx.json.json 4 | import io.ktor.server.application.Application 5 | import io.ktor.server.application.install 6 | import io.ktor.server.engine.* 7 | import io.ktor.server.cio.* 8 | import io.ktor.server.plugins.contentnegotiation.ContentNegotiation 9 | import io.ktor.server.response.respond 10 | import io.ktor.server.routing.get 11 | import io.ktor.server.routing.routing 12 | import kotlinx.serialization.Serializable 13 | 14 | /** 15 | * Generic wrapper to test KotlinX Serialization on GraalVM 16 | */ 17 | @Serializable 18 | data class JsonBody(val body: A) 19 | 20 | fun main() { 21 | embeddedServer(CIO, port = 8080, host = "0.0.0.0", module = Application::module) 22 | .start(wait = true) 23 | } 24 | 25 | fun Application.module() { 26 | install(ContentNegotiation) { json() } 27 | routing { 28 | get("/") { 29 | call.respond(JsonBody("Hello GraalVM!")) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /graalvm/src/main/resources/META-INF/native-image/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl", 4 | "allDeclaredConstructors":true 5 | }, 6 | { 7 | "name": "kotlin.KotlinVersion", 8 | "allPublicMethods": true, 9 | "allDeclaredFields":true, 10 | "allDeclaredMethods":true, 11 | "allDeclaredConstructors":true 12 | }, 13 | { 14 | "name": "kotlin.KotlinVersion[]" 15 | }, 16 | { 17 | "name": "kotlin.KotlinVersion$Companion" 18 | }, 19 | { 20 | "name": "kotlin.KotlinVersion$Companion[]" 21 | }, 22 | { 23 | "name": "kotlin.internal.jdk8.JDK8PlatformImplementations", 24 | "allPublicMethods": true, 25 | "allDeclaredFields":true, 26 | "allDeclaredMethods":true, 27 | "allDeclaredConstructors":true 28 | } 29 | ] -------------------------------------------------------------------------------- /graalvm/src/test/kotlin/io/ktorgraal/ConfigureRoutingTest.kt: -------------------------------------------------------------------------------- 1 | package io.ktorgraal 2 | 3 | import io.ktor.client.call.body 4 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation 5 | import io.ktor.client.request.* 6 | import io.ktor.serialization.kotlinx.json.json 7 | import io.ktor.server.testing.* 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | 11 | class ConfigureRoutingTest { 12 | 13 | @Test 14 | fun testGetHi() = testApplication { 15 | application { module() } 16 | val client = createClient { 17 | install(ContentNegotiation) { json() } 18 | } 19 | val response = client.get("/") 20 | val actual = response.body>() 21 | assertEquals(JsonBody("Hello GraalVM!"), actual) 22 | } 23 | } -------------------------------------------------------------------------------- /graalvm/src/test/resources/META-INF/native-image/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "kotlin.internal.jdk8.JDK8PlatformImplementations", 4 | "allDeclaredConstructors": true, 5 | "allPublicConstructors": true, 6 | "allDeclaredFields": true, 7 | "allPublicFields": true, 8 | "allDeclaredMethods": true, 9 | "allPublicMethods": true 10 | }, 11 | { 12 | "name": "io.ktor.utils.io.pool.DefaultPool", 13 | "fields": [ 14 | { 15 | "name": "top", 16 | "allowUnsafeAccess": true 17 | } 18 | ] 19 | }, 20 | { 21 | "name": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl", 22 | "allDeclaredConstructors":true 23 | }, 24 | { 25 | "name": "kotlin.KotlinVersion[]" 26 | }, 27 | { 28 | "name": "kotlin.KotlinVersion$Companion" 29 | }, 30 | { 31 | "name": "kotlin.KotlinVersion$Companion[]" 32 | } 33 | ] -------------------------------------------------------------------------------- /graalvm/src/test/resources/META-INF/native-image/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources":[ 3 | {"pattern":"META-INF/.*.kotlin_module$"}, 4 | {"pattern":"META-INF/services/.*"}, 5 | {"pattern":".*.kotlin_builtins"} 6 | ] 7 | } -------------------------------------------------------------------------------- /h2/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | bin/ 16 | !**/src/main/**/bin/ 17 | !**/src/test/**/bin/ 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | out/ 25 | !**/src/main/**/out/ 26 | !**/src/test/**/out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ -------------------------------------------------------------------------------- /h2/README.md: -------------------------------------------------------------------------------- 1 | # Message board 2 | 3 | A message board application that uses the H2 in-memory database as a storage. 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. -------------------------------------------------------------------------------- /h2/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | application 6 | kotlin("jvm") version "2.1.20" 7 | id("io.ktor.plugin") version "3.1.3" 8 | } 9 | 10 | group = "io.ktor.samples" 11 | version = "0.0.1" 12 | application { 13 | mainClass.set("io.ktor.server.netty.EngineMain") 14 | 15 | val isDevelopment: Boolean = project.ext.has("development") 16 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") } 22 | } 23 | 24 | dependencies { 25 | implementation("com.zaxxer:HikariCP:5.0.1") 26 | implementation("com.h2database:h2:2.1.214") 27 | 28 | implementation("io.ktor:ktor-server-core-jvm") 29 | implementation("io.ktor:ktor-server-netty-jvm") 30 | implementation("ch.qos.logback:logback-classic:$logback_version") 31 | implementation("io.ktor:ktor-server-html-builder") 32 | testImplementation("io.ktor:ktor-server-test-host-jvm") 33 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 34 | } 35 | -------------------------------------------------------------------------------- /h2/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /h2/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/h2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /h2/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /h2/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "h2" 2 | -------------------------------------------------------------------------------- /h2/src/main/kotlin/io/ktor/samples/Application.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples 2 | 3 | import io.ktor.samples.plugins.* 4 | import io.ktor.server.application.* 5 | 6 | fun main(args: Array): Unit = 7 | io.ktor.server.netty.EngineMain.main(args) 8 | 9 | @Suppress("unused") 10 | fun Application.module() { 11 | configureRouting() 12 | } 13 | -------------------------------------------------------------------------------- /h2/src/main/kotlin/io/ktor/samples/H2.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples 2 | 3 | import com.zaxxer.hikari.HikariConfig 4 | import com.zaxxer.hikari.HikariDataSource 5 | import io.ktor.server.application.* 6 | import io.ktor.server.config.* 7 | 8 | fun Application.h2(): HikariDataSource { 9 | val config = HikariConfig().apply { 10 | driverClassName = "org.h2.Driver" 11 | jdbcUrl = "jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1" 12 | } 13 | 14 | return HikariDataSource(config) 15 | } 16 | 17 | private fun HikariConfig.readH2ConfigFromProperties(properties: ApplicationConfig) { 18 | properties.propertyOrNull("jdbcUrl")?.let { jdbcUrl = it.getString() } 19 | properties.propertyOrNull("username")?.let { username = it.getString() } 20 | properties.propertyOrNull("password")?.let { password = it.getString() } 21 | } 22 | -------------------------------------------------------------------------------- /h2/src/main/kotlin/io/ktor/samples/plugins/Routing.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.plugins 2 | 3 | import io.ktor.http.* 4 | 5 | import io.ktor.server.application.* 6 | import io.ktor.server.html.* 7 | import io.ktor.server.request.* 8 | import io.ktor.server.response.* 9 | import io.ktor.server.routing.* 10 | import kotlinx.html.* 11 | 12 | fun Application.configureRouting() { 13 | val board = MessageBoard(this) 14 | 15 | routing { 16 | get("/") { 17 | val messages = board.list() 18 | 19 | call.respondHtml { 20 | head { title("Message Board") } 21 | 22 | body { 23 | h1 { +"Message Board" } 24 | h2 { +"Post Message" } 25 | form { 26 | action = "/yell" 27 | method = FormMethod.post 28 | textInput { name = "text"; placeholder = "Message text" } 29 | submitInput { value = "Yell" } 30 | } 31 | h2 { +"Messages" } 32 | ul { 33 | messages.forEach { 34 | li { +it.toString() } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | get("/messages") { 42 | val messages = board.list() 43 | call.respondText(messages.joinToString()) 44 | } 45 | post("/yell") { 46 | val text = call.receiveParameters()["text"] ?: error("Missing text parameter") 47 | board.post(text) 48 | call.respondRedirect("/") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /h2/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | port = ${?PORT} 5 | } 6 | application { 7 | modules = [ io.ktor.samples.ApplicationKt.module ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /h2/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /h2/src/test/kotlin/io/ktor/samples/ApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples 2 | 3 | import io.ktor.http.* 4 | import io.ktor.samples.plugins.* 5 | import io.ktor.server.application.* 6 | import io.ktor.server.request.* 7 | import io.ktor.server.response.* 8 | import io.ktor.server.routing.* 9 | import io.ktor.server.testing.* 10 | import kotlin.test.* 11 | 12 | class ApplicationTest { 13 | } 14 | -------------------------------------------------------------------------------- /httpbin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.server.netty.EngineMain") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-server-core-jvm") 19 | implementation("io.ktor:ktor-server-netty-jvm") 20 | implementation("io.ktor:ktor-server-auth") 21 | implementation("io.ktor:ktor-server-auto-head-response") 22 | implementation("io.ktor:ktor-server-call-logging") 23 | implementation("io.ktor:ktor-server-compression") 24 | implementation("io.ktor:ktor-server-conditional-headers") 25 | implementation("io.ktor:ktor-server-content-negotiation") 26 | implementation("io.ktor:ktor-server-cors") 27 | implementation("io.ktor:ktor-server-default-headers") 28 | implementation("io.ktor:ktor-serialization-gson") 29 | implementation("io.ktor:ktor-server-sessions-jvm") 30 | implementation("io.ktor:ktor-server-status-pages") 31 | implementation("io.ktor:ktor-server-html-builder") 32 | implementation("io.ktor:ktor-server-partial-content") 33 | implementation("ch.qos.logback:logback-classic:$logback_version") 34 | testImplementation("io.ktor:ktor-server-test-host-jvm") 35 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 36 | } 37 | 38 | java { 39 | toolchain { 40 | languageVersion.set(JavaLanguageVersion.of(8)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /httpbin/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | 5 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 6 | 7 | -------------------------------------------------------------------------------- /httpbin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/httpbin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /httpbin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /httpbin/request.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:8080/post 2 | Content-Type: text/plain 3 | 4 | Hello, world! 5 | 6 | ### 7 | POST http://localhost:8080/post 8 | Content-Type: application/x-www-form-urlencoded 9 | 10 | username=JetBrains&email=example@jetbrains.com&password=foobar&confirmation=foobar 11 | 12 | ### 13 | POST http://0.0.0.0:8080/post 14 | Content-Type: application/json 15 | 16 | { 17 | "id": 3, 18 | "firstName" : "Jet", 19 | "lastName": "Brains" 20 | } -------------------------------------------------------------------------------- /httpbin/src/main/kotlin/io/ktor/samples/httpbin/Helpers.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.httpbin 2 | 3 | import io.ktor.server.plugins.* 4 | import io.ktor.http.* 5 | import io.ktor.server.request.* 6 | import kotlinx.html.* 7 | 8 | /** 9 | * Helper function to build the full url of a [ApplicationRequest]. 10 | */ 11 | fun ApplicationRequest.url(): String { 12 | val port = when (origin.remotePort) { 13 | in listOf(80, 443) -> "" 14 | else -> ":${origin.remotePort}" 15 | } 16 | return "${origin.scheme}://${origin.remoteHost}$port${origin.uri}" 17 | } 18 | 19 | /** /links/:n/:m **/ 20 | fun HTML.generateLinks(nbLinks: Int, selectedLink: Int) { 21 | head { 22 | title { 23 | +"Links" 24 | } 25 | } 26 | body { 27 | ul { 28 | for (i in 0 until nbLinks) { 29 | li { 30 | a { 31 | if (i != selectedLink) { 32 | attributes["href"] = "/links/$nbLinks/$i" 33 | } 34 | +"Link $i" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | fun HTML.invalidRequest(message: String) { 43 | head { 44 | title { 45 | +"Invalid Request" 46 | } 47 | } 48 | body { 49 | h1 { 50 | +"Invalid request" 51 | } 52 | p { 53 | code { +message } 54 | } 55 | } 56 | } 57 | 58 | 59 | data class ImageConfig(val path: String, val contentType: ContentType, val filename: String) 60 | 61 | /** For /deny **/ 62 | val ANGRY_ASCII = """ 63 | .-''''''-. 64 | .' _ _ '. 65 | / O O \\ 66 | : : 67 | | | 68 | : __ : 69 | \ .-"` `"-. / 70 | '. .' 71 | '-......-' 72 | YOU SHOULDN'T BE HERE 73 | """ 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /httpbin/src/main/kotlin/io/ktor/samples/httpbin/HttpBinError.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.httpbin 2 | 3 | import io.ktor.http.* 4 | 5 | data class HttpBinError( 6 | val request: String, 7 | val message: String, 8 | val code: HttpStatusCode, 9 | val cause: Throwable? = null 10 | ) -------------------------------------------------------------------------------- /httpbin/src/main/kotlin/io/ktor/samples/httpbin/HttpBinResponse.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.httpbin 2 | 3 | import io.ktor.http.* 4 | import io.ktor.http.content.* 5 | import io.ktor.server.application.* 6 | import io.ktor.server.plugins.* 7 | import io.ktor.server.response.* 8 | 9 | class HttpBinResponse( 10 | var parameters: Parameters? = null, 11 | var headers: Map>? = null, 12 | var origin: String? = null, 13 | var url: String? = null, 14 | var `user-agent`: String? = null, 15 | var data: String? = null, 16 | var files: Map? = null, 17 | var form: Parameters? = null, 18 | val json: Map? = null, 19 | var gzipped: Boolean? = null, 20 | var deflated: Boolean? = null, 21 | var method: String? = null, 22 | var cookies: Map? = null 23 | ) 24 | 25 | fun HttpBinResponse.clear() { 26 | parameters = null 27 | headers = null 28 | url = null 29 | origin = null 30 | method = null 31 | } 32 | 33 | /** 34 | * By default, send what is expected for /get 35 | * Use a lambda to customize the response 36 | **/ 37 | suspend fun ApplicationCall.sendHttpBinResponse(configure: suspend HttpBinResponse.() -> Unit = {}) { 38 | val response = HttpBinResponse( 39 | parameters = request.queryParameters, 40 | url = request.url(), 41 | origin = request.origin.remoteHost, 42 | method = request.origin.method.value 43 | ) 44 | response.configure() 45 | respond(response) 46 | } 47 | -------------------------------------------------------------------------------- /httpbin/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | } 5 | 6 | application { 7 | modules = [ io.ktor.samples.httpbin.HttpBinApplicationKt.main ] 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /httpbin/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /httpbin/src/main/resources/static/LICENSE: -------------------------------------------------------------------------------- 1 | Those assets were created by the httpbin open-source project 2 | 3 | https://github.com/Runscope/httpbin 4 | 5 | ----- 6 | 7 | ISC License 8 | 9 | Copyright (c) 2011 Kenneth Reitz. 10 | 11 | Permission to use, copy, modify, and/or distribute this software for any 12 | purpose with or without fee is hereby granted, provided that the above 13 | copyright notice and this permission notice appear in all copies. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 16 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 17 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 18 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 19 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 20 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 21 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /httpbin/src/main/resources/static/forms-post.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

9 |

10 |

11 |
12 | Pizza Size 13 |

14 |

15 |

16 |
17 |
18 | Pizza Toppings 19 |

20 |

21 |

22 |

23 |
24 |

25 |

26 |

27 | 28 |

29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /httpbin/src/main/resources/static/jackal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/httpbin/src/main/resources/static/jackal.jpg -------------------------------------------------------------------------------- /httpbin/src/main/resources/static/pig_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/httpbin/src/main/resources/static/pig_icon.png -------------------------------------------------------------------------------- /httpbin/src/main/resources/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /deny -------------------------------------------------------------------------------- /httpbin/src/main/resources/static/sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | Wake up to WonderWidgets! 14 | 15 | 16 | 17 | 18 | Overview 19 | Why WonderWidgets are great 20 | 21 | Who buys WonderWidgets 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /httpbin/src/main/resources/static/wolf_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/httpbin/src/main/resources/static/wolf_1.webp -------------------------------------------------------------------------------- /httpbin/src/test/kotlin/io/ktor/samples/httpbin/HttpBinApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.httpbin 2 | 3 | import io.ktor.client.request.* 4 | import io.ktor.server.config.ApplicationConfig 5 | import io.ktor.server.testing.* 6 | import org.junit.Test 7 | import kotlin.test.* 8 | 9 | /** 10 | * Tests the HttpBinApplication. 11 | */ 12 | class HttpBinApplicationTest { 13 | /** 14 | * Tests the redirect route by checking its behaviour. 15 | */ 16 | @Test 17 | fun testRedirect() { 18 | testApplication { 19 | environment { 20 | config = ApplicationConfig(null) 21 | } 22 | val client = createClient { 23 | followRedirects = false 24 | } 25 | client.get("/redirect/2").apply { 26 | assertEquals("/redirect/1", headers["Location"]) 27 | } 28 | client.get("/redirect/1").apply { 29 | assertEquals("/redirect/0", headers["Location"]) 30 | } 31 | client.get("/redirect/0").apply { 32 | assertEquals(null, headers["Location"]) 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jwt-auth-tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests for API secured with RSA-signed JWT 2 | 3 | A sample project is to show how to write tests for an API secured with RSA-signed [JWT](https://auth0.com/docs/secure/tokens/json-web-tokens). 4 | 5 | ## Running 6 | 7 | To run the tests execute: 8 | ```shell 9 | ./gradlew test 10 | ``` 11 | 12 | To run the application execute: 13 | ```shell 14 | ./gradlew run 15 | ``` 16 | -------------------------------------------------------------------------------- /jwt-auth-tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.20" 3 | kotlin("plugin.serialization") version "2.1.20" 4 | id("io.ktor.plugin") version "3.1.3" 5 | } 6 | 7 | application { 8 | mainClass.set("io.ktor.samples.jwtauth.MainKt") 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation("io.ktor:ktor-server-core") 17 | implementation("io.ktor:ktor-server-netty") 18 | implementation("io.ktor:ktor-server-auth") 19 | implementation("io.ktor:ktor-server-auth-jwt") 20 | implementation("io.ktor:ktor-server-content-negotiation") 21 | implementation("io.ktor:ktor-serialization-kotlinx-json") 22 | implementation("ch.qos.logback:logback-classic:1.5.12") 23 | testImplementation("io.ktor:ktor-server-test-host") 24 | testImplementation(kotlin("test")) 25 | } 26 | 27 | tasks.withType { 28 | useJUnitPlatform() 29 | } 30 | -------------------------------------------------------------------------------- /jwt-auth-tests/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/jwt-auth-tests/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /jwt-auth-tests/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /jwt-auth-tests/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "jwt-auth-tests" 2 | -------------------------------------------------------------------------------- /jwt-auth-tests/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | } 5 | 6 | application { 7 | modules = [ io.ktor.samples.jwtauth.MainKt.main ] 8 | } 9 | } 10 | 11 | jwt { 12 | privateKey = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAtfJaLrzXILUg1U3N1KV8yJr92GHn5OtYZR7qWk1Mc4cy4JGjklYup7weMjBD9f3bBVoIsiUVX6xNcYIr0Ie0AQIDAQABAkEAg+FBquToDeYcAWBe1EaLVyC45HG60zwfG1S4S3IB+y4INz1FHuZppDjBh09jptQNd+kSMlG1LkAc/3znKTPJ7QIhANpyB0OfTK44lpH4ScJmCxjZV52mIrQcmnS3QzkxWQCDAiEA1Tn7qyoh+0rOO/9vJHP8U/beo51SiQMw0880a1UaiisCIQDNwY46EbhGeiLJR1cidr+JHl86rRwPDsolmeEF5AdzRQIgK3KXL3d0WSoS//K6iOkBX3KMRzaFXNnDl0U/XyeGMuUCIHaXv+n+Brz5BDnRbWS+2vkgIe9bUNlkiArpjWvX+2we" 13 | issuer = "http://0.0.0.0:8080/" 14 | audience = "http://0.0.0.0:8080/hello" 15 | realm = "Access to 'hello'" 16 | } -------------------------------------------------------------------------------- /jwt-auth-tests/src/main/resources/jwks.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "kty": "RSA", 5 | "e": "AQAB", 6 | "kid": "6f8856ed-9189-488f-9011-0ff4b6c08edc", 7 | "n":"tfJaLrzXILUg1U3N1KV8yJr92GHn5OtYZR7qWk1Mc4cy4JGjklYup7weMjBD9f3bBVoIsiUVX6xNcYIr0Ie0AQ" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /ktor-client-wasm/.fleet/receipt.json: -------------------------------------------------------------------------------- 1 | // Project generated by Kotlin Multiplatform Wizard 2 | { 3 | "spec": { 4 | "template_id": "kmt", 5 | "targets": { 6 | "web": { 7 | "ui": [ 8 | "compose" 9 | ] 10 | } 11 | } 12 | }, 13 | "timestamp": "2024-03-22T09:05:07.407317266Z" 14 | } -------------------------------------------------------------------------------- /ktor-client-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | **/build/ 4 | xcuserdata 5 | !src/**/build/ 6 | local.properties 7 | .idea 8 | .DS_Store 9 | captures 10 | .externalNativeBuild 11 | .cxx 12 | *.xcodeproj/* 13 | !*.xcodeproj/project.pbxproj 14 | !*.xcodeproj/xcshareddata/ 15 | !*.xcodeproj/project.xcworkspace/ 16 | !*.xcworkspace/contents.xcworkspacedata 17 | **/xcshareddata/WorkspaceSettings.xcsettings 18 | -------------------------------------------------------------------------------- /ktor-client-wasm/README.md: -------------------------------------------------------------------------------- 1 | # Ktor-client-wasm 2 | 3 | A compose wasm-js application using Ktor HttpClient to make requests. 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew clean wasmJsBrowserRun 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 14 | -------------------------------------------------------------------------------- /ktor-client-wasm/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | // this is necessary to avoid the plugins to be loaded multiple times 3 | // in each subproject's classloader 4 | alias(libs.plugins.jetbrainsCompose) apply false 5 | alias(libs.plugins.kotlinMultiplatform) apply false 6 | } -------------------------------------------------------------------------------- /ktor-client-wasm/composeApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig 2 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 3 | 4 | plugins { 5 | alias(libs.plugins.kotlinMultiplatform) 6 | alias(libs.plugins.composeCompiler) 7 | alias(libs.plugins.jetbrainsCompose) 8 | } 9 | 10 | repositories { 11 | maven("https://maven.pkg.jetbrains.space/public/p/ktor/eap") 12 | mavenCentral() 13 | google() 14 | } 15 | 16 | kotlin { 17 | @OptIn(ExperimentalWasmDsl::class) 18 | wasmJs { 19 | moduleName = "composeApp" 20 | browser { 21 | commonWebpackConfig { 22 | outputFileName = "composeApp.js" 23 | devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { 24 | static = (static ?: mutableListOf()).apply { 25 | // Serve sources to debug inside browser 26 | add(project.projectDir.path) 27 | } 28 | } 29 | } 30 | } 31 | binaries.executable() 32 | } 33 | 34 | sourceSets { 35 | commonMain.dependencies { 36 | implementation(compose.runtime) 37 | implementation(compose.foundation) 38 | implementation(compose.material) 39 | implementation(compose.ui) 40 | implementation(compose.components.resources) 41 | implementation(compose.components.uiToolingPreview) 42 | 43 | implementation(libs.ktorClientCore) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ktor-client-wasm/composeApp/src/wasmJsMain/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.AnimatedVisibility 2 | import androidx.compose.foundation.Image 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.material.Button 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.* 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.asComposeImageBitmap 12 | import androidx.compose.ui.graphics.painter.BitmapPainter 13 | import io.ktor.client.* 14 | import io.ktor.client.call.* 15 | import io.ktor.client.request.* 16 | import org.jetbrains.skia.Bitmap 17 | 18 | val client = HttpClient() 19 | 20 | @Composable 21 | fun UrlImage(url: String) { 22 | var painter by remember { mutableStateOf(null) } 23 | 24 | LaunchedEffect(Unit) { 25 | val response: ByteArray = client.get(url) 26 | .body() 27 | 28 | val image = org.jetbrains.skia.Image.makeFromEncoded(response) 29 | val bitmap = Bitmap.Companion 30 | .makeFromImage(image) 31 | .asComposeImageBitmap() 32 | painter = BitmapPainter(bitmap) 33 | } 34 | 35 | val imagePainter = painter ?: return 36 | Image( 37 | painter = imagePainter, 38 | contentDescription = url 39 | ) 40 | } 41 | 42 | @Composable 43 | fun App() { 44 | MaterialTheme { 45 | var showContent by remember { mutableStateOf(false) } 46 | Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { 47 | Button(onClick = { showContent = !showContent }) { 48 | Text("Click me!") 49 | } 50 | AnimatedVisibility(showContent) { 51 | UrlImage("/kodee.png") 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /ktor-client-wasm/composeApp/src/wasmJsMain/kotlin/Greeting.kt: -------------------------------------------------------------------------------- 1 | class Greeting { 2 | private val platform = getPlatform() 3 | 4 | fun greet(): String { 5 | return "Hello, ${platform.name}!" 6 | } 7 | } -------------------------------------------------------------------------------- /ktor-client-wasm/composeApp/src/wasmJsMain/kotlin/Platform.kt: -------------------------------------------------------------------------------- 1 | class WasmPlatform { 2 | val name: String = "Web with Kotlin/Wasm" 3 | } 4 | 5 | fun getPlatform() = WasmPlatform() -------------------------------------------------------------------------------- /ktor-client-wasm/composeApp/src/wasmJsMain/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.ExperimentalComposeUiApi 2 | import androidx.compose.ui.window.CanvasBasedWindow 3 | 4 | @OptIn(ExperimentalComposeUiApi::class) 5 | fun main() { 6 | CanvasBasedWindow(canvasElementId = "ComposeTarget") { App() } 7 | } -------------------------------------------------------------------------------- /ktor-client-wasm/composeApp/src/wasmJsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Compose App 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ktor-client-wasm/composeApp/src/wasmJsMain/resources/kodee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/ktor-client-wasm/composeApp/src/wasmJsMain/resources/kodee.png -------------------------------------------------------------------------------- /ktor-client-wasm/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | #Gradle 4 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" 5 | 6 | 7 | #Compose 8 | org.jetbrains.compose.experimental.wasm.enabled=true 9 | 10 | #Development 11 | development=true -------------------------------------------------------------------------------- /ktor-client-wasm/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | compose = "1.6.2" 3 | compose-plugin = "1.7.0" 4 | junit = "4.13.2" 5 | kotlin = "2.1.20" 6 | ktor = "3.1.3" 7 | 8 | [libraries] 9 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 10 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } 11 | junit = { group = "junit", name = "junit", version.ref = "junit" } 12 | compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } 13 | compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } 14 | ktorClientCore = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } 15 | 16 | [plugins] 17 | jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } 18 | composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 19 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } -------------------------------------------------------------------------------- /ktor-client-wasm/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/ktor-client-wasm/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ktor-client-wasm/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /ktor-client-wasm/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "WasmProject" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | repositories { 6 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 7 | google() 8 | gradlePluginPortal() 9 | mavenCentral() 10 | } 11 | } 12 | 13 | dependencyResolutionManagement { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 18 | } 19 | } 20 | 21 | include(":composeApp") -------------------------------------------------------------------------------- /kweet/README.md: -------------------------------------------------------------------------------- 1 | # Kweet 2 | 3 | A messaging application that uses [FreeMarker](https://ktor.io/docs/freemarker.html) templates and the [Resources](https://ktor.io/docs/type-safe-routing.html) plugin. 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 14 | -------------------------------------------------------------------------------- /kweet/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | val exposed_version: String by project 4 | 5 | plugins { 6 | kotlin("jvm") version "2.1.20" 7 | id("io.ktor.plugin") version "3.1.3" 8 | kotlin("plugin.serialization") version "2.1.20" 9 | } 10 | 11 | application { 12 | mainClass.set("io.ktor.server.netty.EngineMain") 13 | } 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | implementation("io.ktor:ktor-server-freemarker") 21 | implementation("io.ktor:ktor-server-resources") 22 | implementation("io.ktor:ktor-server-sessions") 23 | implementation("io.ktor:ktor-server-conditional-headers") 24 | implementation("io.ktor:ktor-server-default-headers") 25 | implementation("io.ktor:ktor-server-partial-content") 26 | implementation("io.ktor:ktor-server-call-logging") 27 | implementation("ch.qos.logback:logback-classic:$logback_version") 28 | implementation("com.h2database:h2:2.1.214") 29 | implementation("com.mchange:c3p0:0.9.5.5") 30 | implementation("org.jetbrains.exposed:exposed-core:$exposed_version") 31 | implementation("org.jetbrains.exposed:exposed-dao:$exposed_version") 32 | implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version") 33 | implementation("org.jetbrains.exposed:exposed-jodatime:$exposed_version") 34 | implementation("joda-time:joda-time:2.12.2") 35 | implementation("org.freemarker:freemarker:2.3.32") 36 | implementation("org.ehcache:ehcache:3.9.7") 37 | implementation("io.ktor:ktor-server-netty-jvm") 38 | testImplementation("io.mockk:mockk:1.14.0") 39 | testImplementation("org.jetbrains.kotlin:kotlin-test") 40 | testImplementation("io.ktor:ktor-server-test-host-jvm") 41 | } 42 | -------------------------------------------------------------------------------- /kweet/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | exposed_version=0.40.1 4 | kotlin.code.style=official 5 | -------------------------------------------------------------------------------- /kweet/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/kweet/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /kweet/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/Delete.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet 2 | 3 | import io.ktor.http.* 4 | import io.ktor.samples.kweet.dao.* 5 | import io.ktor.server.application.* 6 | import io.ktor.server.resources.post 7 | import io.ktor.server.request.* 8 | import io.ktor.server.routing.* 9 | import io.ktor.server.sessions.* 10 | 11 | /** 12 | * Registers a route for deleting deleting kweets. 13 | */ 14 | fun Route.delete(dao: DAOFacade, hashFunction: (String) -> String) { 15 | // Uses the Resources plugin to register a 'post' route for '/kweet/{id}/delete'. 16 | post { 17 | // Tries to get (null on failure) the user associated to the current KweetSession 18 | val user = call.sessions.get()?.let { dao.user(it.userId) } 19 | 20 | // Receives the Parameters date and code, if any of those fails to be obtained, 21 | // it redirects to the tweet page without deleting the kweet. 22 | val post = call.receive() 23 | val date = post["date"]?.toLongOrNull() ?: return@post call.redirect(ViewKweet(it.id)) 24 | val code = post["code"] ?: return@post call.redirect(ViewKweet(it.id)) 25 | val kweet = dao.getKweet(it.id) 26 | 27 | // Verifies that the kweet user matches the session user and that the code and the date match, to prevent CSFR. 28 | if (user == null || kweet.userId != user.userId || !call.verifyCode(date, user, code, hashFunction)) { 29 | call.redirect(ViewKweet(it.id)) 30 | } else { 31 | dao.deleteKweet(it.id) 32 | call.redirect(Index()) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/Index.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet 2 | 3 | import io.ktor.samples.kweet.dao.* 4 | import io.ktor.server.application.* 5 | import io.ktor.server.freemarker.* 6 | import io.ktor.server.resources.* 7 | import io.ktor.server.response.* 8 | import io.ktor.server.routing.* 9 | import io.ktor.server.sessions.* 10 | 11 | /** 12 | * Register the index route of the website. 13 | */ 14 | fun Route.index(dao: DAOFacade) { 15 | // Uses the Location plugin to register a get route for '/'. 16 | get { 17 | // Tries to get the user from the session (null if failure) 18 | val user = call.sessions.get()?.let { dao.user(it.userId) } 19 | 20 | // Obtains several lists of kweets using different sorting and filters. 21 | val top = dao.top(10).map { dao.getKweet(it) } 22 | val latest = dao.latest(10).map { dao.getKweet(it) } 23 | 24 | // Generates an ETag unique string for this route that will be used for caching. 25 | val etagString = 26 | user?.userId + "," + top.joinToString { it.id.toString() } + latest.joinToString { it.id.toString() } 27 | val etag = etagString.hashCode() 28 | 29 | // Uses FreeMarker to render the page. 30 | call.respond( 31 | FreeMarkerContent( 32 | "index.ftl", 33 | mapOf("top" to top, "latest" to latest, "user" to user), 34 | etag.toString() 35 | ) 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/Styles.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet 2 | 3 | import io.ktor.resources.* 4 | import io.ktor.server.application.* 5 | import io.ktor.server.http.content.* 6 | import io.ktor.server.resources.* 7 | import io.ktor.server.response.* 8 | import io.ktor.server.routing.* 9 | import kotlinx.serialization.* 10 | 11 | @Resource("/styles/main.css") 12 | class MainCss() 13 | 14 | /** 15 | * Register the styles, [MainCss] route (/styles/main.css) 16 | */ 17 | fun Route.styles() { 18 | /** 19 | * On a GET request to the [MainCss] route, it returns the `blog.css` file from the resources. 20 | * 21 | * Here we could preprocess or join several CSS/SASS/LESS. 22 | */ 23 | get { 24 | call.respond(call.resolveResource("blog.css")!!) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/UserPage.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet 2 | 3 | import io.ktor.http.* 4 | import io.ktor.samples.kweet.dao.* 5 | import io.ktor.server.application.* 6 | import io.ktor.server.freemarker.* 7 | import io.ktor.server.resources.* 8 | import io.ktor.server.response.* 9 | import io.ktor.server.routing.* 10 | import io.ktor.server.sessions.* 11 | 12 | /** 13 | * Register the [UserPage] route '/user/{user}', 14 | * with the user profile. 15 | */ 16 | fun Route.userPage(dao: DAOFacade) { 17 | /** 18 | * A GET request will return a page with the profile of a given user from its [UserPage.user] name. 19 | * If the user doesn't exist, it will return a 404 page instead. 20 | */ 21 | get { 22 | val user = call.sessions.get()?.let { dao.user(it.userId) } 23 | val pageUser = dao.user(it.user) 24 | 25 | if (pageUser == null) { 26 | call.respond(HttpStatusCode.NotFound.description("User ${it.user} doesn't exist")) 27 | } else { 28 | val kweets = dao.userKweets(it.user).map { dao.getKweet(it) } 29 | val etag = (user?.userId ?: "") + "_" + kweets.map { it.text.hashCode() }.hashCode().toString() 30 | 31 | call.respond( 32 | FreeMarkerContent( 33 | "user.ftl", 34 | mapOf("user" to user, "pageUser" to pageUser, "kweets" to kweets), 35 | etag 36 | ) 37 | ) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/ViewKweet.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet 2 | 3 | import io.ktor.samples.kweet.dao.* 4 | import io.ktor.server.application.* 5 | import io.ktor.server.freemarker.* 6 | import io.ktor.server.resources.* 7 | import io.ktor.server.response.* 8 | import io.ktor.server.routing.* 9 | import io.ktor.server.sessions.* 10 | 11 | /** 12 | * Registers the [ViewKweet] route. (/kweet/{id}) 13 | */ 14 | fun Route.viewKweet(dao: DAOFacade, hashFunction: (String) -> String) { 15 | /** 16 | * This page shows the [Kweet] content and its replies. 17 | * If there is a user logged in, and the kweet is from her/him, it will provide secured links to remove it. 18 | */ 19 | get { 20 | val user = call.sessions.get()?.let { dao.user(it.userId) } 21 | val date = System.currentTimeMillis() 22 | val code = if (user != null) call.securityCode(date, user, hashFunction) else null 23 | 24 | call.respond( 25 | FreeMarkerContent( 26 | "view-kweet.ftl", 27 | mapOf("user" to user, "kweet" to dao.getKweet(it.id), "date" to date, "code" to code), 28 | user?.userId ?: "" 29 | ) 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/dao/Kweets.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet.dao 2 | 3 | import org.jetbrains.exposed.sql.* 4 | import org.jetbrains.exposed.sql.jodatime.* 5 | 6 | /** 7 | * Represents the Kweets table using Exposed as DAO. 8 | */ 9 | object Kweets : Table() { 10 | val id = integer("id").autoIncrement() 11 | val user = varchar("user_id", 20).index() 12 | val date = datetime("date") 13 | val replyTo = integer("reply_to").index().nullable() 14 | val directReplyTo = integer("direct_reply_to").index().nullable() 15 | val text = varchar("text", 1024) 16 | } 17 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/dao/Users.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet.dao 2 | 3 | import org.jetbrains.exposed.sql.* 4 | 5 | /** 6 | * Represents the Users table using Exposed as DAO. 7 | */ 8 | object Users : Table() { 9 | val id = varchar("id", 20) 10 | val email = varchar("email", 128).uniqueIndex() 11 | val displayName = varchar("display_name", 256) 12 | val passwordHash = varchar("password_hash", 64) 13 | } 14 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/model/Kweet.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet.model 2 | 3 | import org.joda.time.* 4 | import java.io.* 5 | 6 | data class Kweet(val id: Int, val userId: String, val text: String, val date: DateTime, val replyTo: Int?) : 7 | Serializable 8 | -------------------------------------------------------------------------------- /kweet/src/main/kotlin/io/ktor/samples/kweet/model/User.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.kweet.model 2 | 3 | import java.io.* 4 | 5 | data class User(val userId: String, val email: String, val displayName: String, val passwordHash: String) : Serializable -------------------------------------------------------------------------------- /kweet/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | } 5 | 6 | application { 7 | modules = [ io.ktor.samples.kweet.KweetApplicationKt.main ] 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /kweet/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /kweet/src/main/resources/templates/index.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="top" type="java.util.List" --> 2 | <#-- @ftlvariable name="latest" type="java.util.List" --> 3 | 4 | <#import "template.ftl" as layout /> 5 | 6 | <@layout.mainLayout title="Welcome"> 7 |
8 |

Top 10

9 | <@layout.kweets_list kweets=top> 10 | 11 |

Recent 10

12 | <@layout.kweets_list kweets=latest> 13 |
14 | 15 | -------------------------------------------------------------------------------- /kweet/src/main/resources/templates/login.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="error" type="java.lang.String" --> 2 | <#-- @ftlvariable name="userId" type="java.lang.String" --> 3 | 4 | <#import "template.ftl" as layout /> 5 | 6 | <@layout.mainLayout title="Welcome"> 7 |
8 | <#if error??> 9 |

${error}

10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /kweet/src/main/resources/templates/new-kweet.ftl: -------------------------------------------------------------------------------- 1 | <#import "template.ftl" as layout /> 2 | 3 | <@layout.mainLayout title="New kweet"> 4 |
5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /kweet/src/main/resources/templates/register.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="error" type="java.lang.String" --> 2 | <#-- @ftlvariable name="pageUser" type="io.ktor.samples.kweet.model.User" --> 3 | 4 | <#import "template.ftl" as layout /> 5 | 6 | <@layout.mainLayout title="Welcome"> 7 |
8 | <#if error??> 9 |

${error}

10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 30 | 31 | 32 | 33 |
34 | 35 | -------------------------------------------------------------------------------- /kweet/src/main/resources/templates/user.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="pageUser" type="io.ktor.samples.kweet.model.User" --> 2 | <#-- @ftlvariable name="kweets" type="java.util.List" --> 3 | 4 | <#import "template.ftl" as layout /> 5 | 6 | <@layout.mainLayout title="User ${pageUser.displayName}"> 7 |

User's kweets

8 | 9 | <@layout.kweets_list kweets=kweets> 10 | 11 | -------------------------------------------------------------------------------- /kweet/src/main/resources/templates/view-kweet.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="date" type="java.lang.Long" --> 2 | <#-- @ftlvariable name="code" type="java.lang.String" --> 3 | <#-- @ftlvariable name="kweet" type="io.ktor.samples.kweet.model.Kweet" --> 4 | <#import "template.ftl" as layout /> 5 | 6 | <@layout.mainLayout title="New kweet"> 7 |
8 |
9 | 12 |
13 |
${kweet.text}
14 |
15 | <#if user??> 16 |

17 | Delete kweet 18 |

19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /location-header/README.md: -------------------------------------------------------------------------------- 1 | # Location Header 2 | 3 | An application demonstrating how to use the `Location` HTTP headers. 4 | 5 | ## Running 6 | 7 | Execute this command to run this sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | To work with the demo, you need to make a POST request. If using IntelliJ IDEA, open the [Test.http](Test.http) file to make test requests. 14 | 15 | -------------------------------------------------------------------------------- /location-header/Test.http: -------------------------------------------------------------------------------- 1 | ### Manual setting of Location Header 2 | POST http://localhost:8080/manual 3 | 4 | ### Automatic setting of Location Header 5 | POST http://localhost:8080/extension 6 | -------------------------------------------------------------------------------- /location-header/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.samples.location.LocationHeaderApplicationKt") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-server-core-jvm") 19 | implementation("io.ktor:ktor-server-netty-jvm") 20 | implementation("ch.qos.logback:logback-classic:$logback_version") 21 | } 22 | -------------------------------------------------------------------------------- /location-header/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /location-header/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/location-header/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /location-header/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /location-header/src/main/kotlin/io/ktor/samples/location/LocationHeaderApplication.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.location 2 | 3 | import io.ktor.server.application.* 4 | import io.ktor.http.* 5 | import io.ktor.server.request.* 6 | import io.ktor.server.response.* 7 | import io.ktor.server.routing.* 8 | import io.ktor.server.engine.* 9 | import io.ktor.server.netty.* 10 | 11 | /** 12 | * Main entrypoint of the executable that starts a Netty webserver at port 8080 13 | * and registers the [module]. 14 | * 15 | */ 16 | 17 | fun main() { 18 | embeddedServer(Netty, port = 8080) { module() }.start(wait = true) 19 | } 20 | 21 | fun Application.module() { 22 | routing { 23 | post("/manual") { 24 | // AF4GH is a sample code for demo purposes 25 | call.response.header("Location", "/manual/AF4GH") 26 | call.response.status(HttpStatusCode.Created) 27 | call.respondText("Manually setting the location header") 28 | } 29 | post("/extension") { 30 | // AF4GH is a sample code for demo purposes 31 | call.response.created("AF4GH") 32 | call.respondText("Extension setting the location header") 33 | } 34 | } 35 | } 36 | 37 | private fun ApplicationResponse.created(id: String) { 38 | call.response.status(HttpStatusCode.Created) 39 | call.response.header("Location", "${call.request.uri}/$id") 40 | } 41 | -------------------------------------------------------------------------------- /maven-google-appengine-standard/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/maven-google-appengine-standard/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /maven-google-appengine-standard/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip -------------------------------------------------------------------------------- /maven-google-appengine-standard/README.md: -------------------------------------------------------------------------------- 1 | # Maven Google Appengine Standard 2 | 3 | A sample project for [Ktor](https://ktor.io) running under [Google App Engine](https://cloud.google.com/appengine/) 4 | standard infrastructure with [Maven](https://maven.apache.org) build script. 5 | 6 | ## Prerequisites 7 | 8 | * [Java SDK 8](https://www.oracle.com/technetwork/java/javase/downloads/index.html) or later 9 | * [Google Cloud SDK](https://cloud.google.com/sdk/docs/) 10 | 11 | ## Running 12 | 13 | Run this project under local dev mode with: 14 | 15 | ``` 16 | ./mvnw appengine:run 17 | ``` 18 | 19 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 20 | 21 | ## Deploying 22 | 23 | Use Google Cloud SDK to create application similarly to 24 | [Google App Engine for Java Quickstart](https://cloud.google.com/appengine/docs/standard/java/quickstart): 25 | 26 | Install all the Google Cloud components and login into your account: 27 | 28 | ``` 29 | gcloud init 30 | gcloud components install app-engine-java 31 | gcloud components update 32 | gcloud auth application-default login 33 | ``` 34 | 35 | Create the project and application: 36 | 37 | ``` 38 | gcloud projects create --set-as-default 39 | gcloud app create 40 | ``` 41 | 42 | Then deploy your application with: 43 | 44 | ``` 45 | ./mvnw appengine:deploy 46 | ``` 47 | -------------------------------------------------------------------------------- /maven-google-appengine-standard/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | application { 3 | modules = [ io.ktor.samples.hello.HelloApplicationKt.main ] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /maven-google-appengine-standard/src/HelloApplication.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.hello 2 | 3 | import io.ktor.server.application.* 4 | import io.ktor.server.html.* 5 | import io.ktor.server.routing.* 6 | import kotlinx.html.* 7 | 8 | fun Application.main() { 9 | routing { 10 | get("/") { 11 | call.respondHtml { 12 | head { 13 | title { +"Ktor: maven-google-appengine-standard" } 14 | } 15 | body { 16 | p { 17 | +"Hello from Ktor Maven Google Appengine Standard sample application" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /maven-google-appengine-standard/webapp/WEB-INF/appengine-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | java8 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /maven-google-appengine-standard/webapp/WEB-INF/logging.properties: -------------------------------------------------------------------------------- 1 | .level = INFO 2 | -------------------------------------------------------------------------------- /maven-google-appengine-standard/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | KtorServlet 9 | KtorServlet 10 | io.ktor.server.servlet.ServletApplicationEngine 11 | 12 | 13 | 14 | io.ktor.config 15 | application.conf 16 | 17 | 18 | 19 | 20 | 304857600 21 | 304857600 22 | 0 23 | 24 | 25 | 26 | 27 | KtorServlet 28 | / 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /mongodb/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | bin/ 16 | !**/src/main/**/bin/ 17 | !**/src/test/**/bin/ 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | out/ 25 | !**/src/main/**/out/ 26 | !**/src/test/**/out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ -------------------------------------------------------------------------------- /mongodb/README.md: -------------------------------------------------------------------------------- 1 | # MongoDB sample for Ktor Server 2 | 3 | An application for creating, editing and deleting articles that uses Mongodb running on Docker image as a storage. 4 | 5 | ## Steps 6 | 7 | 1. Execute gradle task `databaseInstance` and wait until Docker Compose builds image and starts container 8 | 9 | 2. Execute this command to run the sample: 10 | 11 | ```bash 12 | ./gradlew run 13 | ``` 14 | 15 | Then, you can open [http://localhost:8080/](http://localhost:8080/) in a browser to create, edit, and delete articles. 16 | -------------------------------------------------------------------------------- /mongodb/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | val mongodb_version: String by project 4 | 5 | plugins { 6 | kotlin("jvm") version "2.1.20" 7 | id("io.ktor.plugin") version "3.1.3" 8 | id("org.jetbrains.kotlin.plugin.serialization") version "2.1.20" 9 | } 10 | 11 | group = "com.example" 12 | version = "0.0.1" 13 | application { 14 | mainClass.set("io.ktor.server.netty.EngineMain") 15 | 16 | val isDevelopment: Boolean = project.ext.has("development") 17 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 18 | } 19 | 20 | repositories { 21 | mavenCentral() 22 | } 23 | 24 | dependencies { 25 | implementation("io.ktor:ktor-server-core-jvm") 26 | implementation("io.ktor:ktor-server-content-negotiation-jvm") 27 | implementation("io.ktor:ktor-serialization-kotlinx-json-jvm") 28 | implementation("io.ktor:ktor-serialization-gson-jvm") 29 | implementation("io.ktor:ktor-server-netty-jvm") 30 | implementation("ch.qos.logback:logback-classic:$logback_version") 31 | implementation("io.ktor:ktor-server-config-yaml") 32 | implementation("org.litote.kmongo:kmongo:$mongodb_version") 33 | testImplementation("io.ktor:ktor-server-test-host-jvm") 34 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 35 | testImplementation("io.ktor:ktor-server-test-host-jvm:3.1.3") 36 | } 37 | 38 | tasks.register("databaseInstance") { 39 | doLast { 40 | val command = arrayOf("docker-compose", "up") 41 | Runtime.getRuntime().exec(command) 42 | } 43 | } -------------------------------------------------------------------------------- /mongodb/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | mongodb: 4 | image: mongodb/mongodb-community-server:6.0-ubi8 5 | volumes: 6 | - mongodata:/data/db 7 | ports: 8 | - 27017:27017 9 | volumes: 10 | mongodata: -------------------------------------------------------------------------------- /mongodb/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | mongodb_version=4.8.0 4 | kotlin.code.style=official 5 | -------------------------------------------------------------------------------- /mongodb/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/mongodb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mongodb/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /mongodb/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ktor-mongodb" -------------------------------------------------------------------------------- /mongodb/src/main/kotlin/com/example/Application.kt: -------------------------------------------------------------------------------- 1 | package com.example 2 | 3 | import io.ktor.server.application.* 4 | import com.example.plugins.* 5 | import com.example.service.ArticleService 6 | 7 | fun main(args: Array): Unit = 8 | io.ktor.server.netty.EngineMain.main(args) 9 | 10 | @Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused. 11 | fun Application.module() { 12 | configureSerialization() 13 | val articleService = ArticleService() 14 | configureRouting(articleService = articleService) 15 | environment.monitor.subscribe(ApplicationStarted) { application -> 16 | application.environment.log.info("Server is started") 17 | } 18 | environment.monitor.subscribe(ApplicationStopped) { application -> 19 | application.environment.log.info("Server is stopped") 20 | articleService.release() 21 | application.environment.monitor.unsubscribe(ApplicationStarted) {} 22 | application.environment.monitor.unsubscribe(ApplicationStopped) {} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mongodb/src/main/kotlin/com/example/entities/Article.kt: -------------------------------------------------------------------------------- 1 | package com.example.entities 2 | 3 | import org.bson.codecs.pojo.annotations.BsonId 4 | import org.litote.kmongo.Id 5 | 6 | data class Article( 7 | @BsonId 8 | val id: Id
? = null, 9 | val title: String, 10 | val body: String 11 | ) 12 | -------------------------------------------------------------------------------- /mongodb/src/main/kotlin/com/example/entities/ArticleExtension.kt: -------------------------------------------------------------------------------- 1 | package com.example.entities 2 | 3 | fun Article.toDto(): CreateArticle = 4 | CreateArticle( 5 | id = this.id.toString(), 6 | title = this.title, 7 | body = this.body 8 | ) 9 | 10 | fun CreateArticle.toArticle(): Article = 11 | Article( 12 | title = this.title, 13 | body = this.body 14 | ) -------------------------------------------------------------------------------- /mongodb/src/main/kotlin/com/example/entities/CreateArticle.kt: -------------------------------------------------------------------------------- 1 | package com.example.entities 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class CreateArticle( 7 | val id: String? = null, 8 | val title: String, 9 | val body: String) 10 | -------------------------------------------------------------------------------- /mongodb/src/main/kotlin/com/example/entities/ErrorResponse.kt: -------------------------------------------------------------------------------- 1 | package com.example.entities 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ErrorResponse(val message: String) { 7 | companion object { 8 | val NOT_FOUND_RESPONSE = ErrorResponse("Article was not found") 9 | val BAD_REQUEST_RESPONSE = ErrorResponse("Invalid request") 10 | } 11 | } -------------------------------------------------------------------------------- /mongodb/src/main/kotlin/com/example/plugins/Serialization.kt: -------------------------------------------------------------------------------- 1 | package com.example.plugins 2 | 3 | import io.ktor.serialization.kotlinx.json.* 4 | import io.ktor.server.plugins.contentnegotiation.* 5 | import io.ktor.server.application.* 6 | 7 | fun Application.configureSerialization() { 8 | install(ContentNegotiation) { 9 | json() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mongodb/src/main/kotlin/com/example/service/ArticleService.kt: -------------------------------------------------------------------------------- 1 | package com.example.service 2 | 3 | import com.example.entities.Article 4 | import org.bson.types.ObjectId 5 | import org.litote.kmongo.* 6 | import org.litote.kmongo.id.toId 7 | 8 | class ArticleService { 9 | private val client = KMongo.createClient() 10 | private val database = client.getDatabase("article") 11 | private val articleCollection = database.getCollection
() 12 | 13 | fun create(article: Article): Id
? { 14 | articleCollection.insertOne(article) 15 | return article.id 16 | } 17 | 18 | fun findAll(): List
= 19 | articleCollection.find() 20 | .toList() 21 | 22 | fun findById(id: String): Article? { 23 | val bsonId: Id
= ObjectId(id).toId() 24 | return articleCollection 25 | .findOne(Article::id eq bsonId) 26 | } 27 | 28 | fun updateArticleById(id: String, request: Article): Boolean = 29 | findById(id) 30 | ?.let { article -> 31 | val updateResult = 32 | articleCollection.replaceOne(article.copy(title = request.title, body = request.body)) 33 | updateResult.modifiedCount == 1L 34 | } ?: false 35 | 36 | fun deleteArticleById(id: String): Boolean { 37 | val bsonId: Id
= ObjectId(id).toId() 38 | val deleteResult = articleCollection.deleteOneById(bsonId) 39 | return deleteResult.deletedCount == 1L 40 | } 41 | 42 | fun release() { 43 | client.close() 44 | } 45 | } -------------------------------------------------------------------------------- /mongodb/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | ktor: 2 | application: 3 | modules: 4 | - com.example.ApplicationKt.module 5 | deployment: 6 | port: 8080 7 | -------------------------------------------------------------------------------- /mongodb/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /mvc-web/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | bin/ 16 | !**/src/main/**/bin/ 17 | !**/src/test/**/bin/ 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | out/ 25 | !**/src/main/**/out/ 26 | !**/src/test/**/out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ -------------------------------------------------------------------------------- /mvc-web/README.md: -------------------------------------------------------------------------------- 1 | # MVC application with Ktor Server 2 | 3 | An application for adding and removing wishes to wishlist that uses Freemarker templates and Exposed. 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to the following pages: 14 | 15 | [http://localhost:8080/wish/list](http://localhost:8080/wish/list) to view and modify the wishlist. 16 | 17 | [http://localhost:8080/wish/topwishes](http://localhost:8080/wish/topwishes) to see the top popular wishes. 18 | -------------------------------------------------------------------------------- /mvc-web/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | val h2_version: String by project 4 | val exposed_version: String by project 5 | 6 | plugins { 7 | kotlin("jvm") version "2.1.20" 8 | id("io.ktor.plugin") version "3.1.3" 9 | id("org.jetbrains.kotlin.plugin.serialization") version "2.1.20" 10 | } 11 | 12 | group = "com.example" 13 | version = "0.0.1" 14 | 15 | application { 16 | mainClass.set("com.example.ApplicationKt") 17 | 18 | val isDevelopment: Boolean = project.ext.has("development") 19 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 20 | } 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") } 25 | } 26 | 27 | dependencies { 28 | implementation("io.ktor:ktor-server-core-jvm") 29 | implementation("io.ktor:ktor-server-config-yaml-jvm") 30 | implementation("org.jetbrains.exposed:exposed-core:$exposed_version") 31 | implementation("org.jetbrains.exposed:exposed-dao:$exposed_version") 32 | implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version") 33 | implementation("com.h2database:h2:$h2_version") 34 | implementation("io.ktor:ktor-server-netty-jvm") 35 | implementation("io.ktor:ktor-server-freemarker") 36 | implementation("ch.qos.logback:logback-classic:$logback_version") 37 | implementation("io.ktor:ktor-server-status-pages") 38 | testImplementation("io.ktor:ktor-server-test-host-jvm") 39 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 40 | } 41 | -------------------------------------------------------------------------------- /mvc-web/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.4.11 3 | exposed_version=0.41.1 4 | h2_version=2.1.214 5 | kotlin.code.style=official 6 | -------------------------------------------------------------------------------- /mvc-web/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/mvc-web/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mvc-web/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /mvc-web/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ktor-mvc" -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/Application.kt: -------------------------------------------------------------------------------- 1 | package com.example 2 | 3 | import com.example.model.DatabaseSingleton 4 | import com.example.plugins.configureRouting 5 | import com.example.plugins.configureTemplating 6 | import io.ktor.server.application.* 7 | import io.ktor.server.netty.* 8 | 9 | fun main(args: Array): Unit = EngineMain.main(args) 10 | 11 | fun Application.module() { 12 | DatabaseSingleton.init( 13 | environment.config.property("ktor.application.datasource.url").getString(), 14 | environment.config.property("ktor.application.datasource.driverClassName").getString() 15 | ) 16 | 17 | configureTemplating() 18 | configureRouting() 19 | } 20 | -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/controller/WishController.kt: -------------------------------------------------------------------------------- 1 | package com.example.controller 2 | 3 | import com.example.model.entity.Wishlist 4 | import com.example.model.repository.TopWishListExampleRepository 5 | import com.example.model.repository.WishListRepository 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | 9 | class WishController { 10 | private val topWishListExampleRepository = TopWishListExampleRepository() 11 | private val wishListRepository = WishListRepository() 12 | 13 | suspend fun addToWishList(wish: String): Wishlist = withContext(Dispatchers.IO) { wishListRepository.addWish(wish) } 14 | 15 | suspend fun getWishList(): List = run { wishListRepository.allWishes() } 16 | 17 | suspend fun deleteFromWishList(recordId: Int) = run { wishListRepository.deleteWish(recordId) } 18 | 19 | fun getTopWishesExample(): List = run { topWishListExampleRepository.topWishes() } 20 | } -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/exceptions/DbElementInsertException.kt: -------------------------------------------------------------------------------- 1 | package com.example.exceptions 2 | 3 | class DbElementInsertException(message: String? = null, throwable: Throwable? = null) : Throwable(message, throwable) -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/model/DatabaseSingleton.kt: -------------------------------------------------------------------------------- 1 | package com.example.model 2 | 3 | import com.example.model.dao.wishlist.Wishlists 4 | import kotlinx.coroutines.Dispatchers 5 | import org.jetbrains.exposed.sql.Database 6 | import org.jetbrains.exposed.sql.SchemaUtils 7 | import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction 8 | import org.jetbrains.exposed.sql.transactions.transaction 9 | 10 | object DatabaseSingleton { 11 | fun init(jdbcURL: String, driverClassName: String) { 12 | val database = Database.connect(jdbcURL, driverClassName) 13 | transaction(database) { 14 | SchemaUtils.create(Wishlists) 15 | } 16 | } 17 | 18 | suspend fun dbQuery(block: suspend () -> T): T = 19 | newSuspendedTransaction(Dispatchers.IO) { block() } 20 | } -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/model/dao/wishlist/WishlistDAOFacade.kt: -------------------------------------------------------------------------------- 1 | package com.example.model.dao.wishlist 2 | 3 | import com.example.model.entity.Wishlist 4 | 5 | interface WishlistDAOFacade { 6 | suspend fun allWishes(): List 7 | suspend fun addWish(wish: String): Wishlist 8 | suspend fun deleteWish(id: Int): Boolean 9 | } -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/model/dao/wishlist/WishlistDAOFacadeImpl.kt: -------------------------------------------------------------------------------- 1 | package com.example.model.dao.wishlist 2 | 3 | import com.example.exceptions.DbElementInsertException 4 | import com.example.model.DatabaseSingleton.dbQuery 5 | import com.example.model.entity.Wishlist 6 | import org.jetbrains.exposed.sql.ResultRow 7 | import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq 8 | import org.jetbrains.exposed.sql.deleteWhere 9 | import org.jetbrains.exposed.sql.insert 10 | import org.jetbrains.exposed.sql.selectAll 11 | 12 | class WishlistDAOFacadeImpl : WishlistDAOFacade { 13 | 14 | private fun resultRowToWish(row: ResultRow) = Wishlist( 15 | id = row[Wishlists.id], 16 | wish = row[Wishlists.wish] 17 | ) 18 | 19 | override suspend fun allWishes(): List = dbQuery { 20 | Wishlists.selectAll().map(::resultRowToWish) 21 | } 22 | 23 | override suspend fun addWish(wish: String): Wishlist = dbQuery { 24 | val insertStatement = Wishlists.insert { 25 | it[Wishlists.wish] = wish 26 | } 27 | val addedWish = insertStatement.resultedValues?.singleOrNull()?.let(::resultRowToWish) 28 | if (addedWish == null) { 29 | throw DbElementInsertException("You've exceeded your wish limit") 30 | } else { 31 | return@dbQuery addedWish 32 | } 33 | } 34 | 35 | override suspend fun deleteWish(id: Int): Boolean = dbQuery { 36 | Wishlists.deleteWhere { Wishlists.id eq id } > 0 37 | } 38 | } -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/model/dao/wishlist/Wishlists.kt: -------------------------------------------------------------------------------- 1 | package com.example.model.dao.wishlist 2 | 3 | import org.jetbrains.exposed.sql.Table 4 | 5 | object Wishlists : Table() { 6 | val id = integer("id").autoIncrement() 7 | val wish = varchar("body", 1024) 8 | 9 | override val primaryKey = PrimaryKey(id) 10 | } -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/model/entity/Wishlist.kt: -------------------------------------------------------------------------------- 1 | package com.example.model.entity 2 | 3 | data class Wishlist(val id :Int,val wish: String) -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/model/repository/TopWishListExampleRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.model.repository 2 | 3 | import com.example.model.entity.Wishlist 4 | 5 | class TopWishListExampleRepository { 6 | fun topWishes(): List = listOf( 7 | Wishlist(0, "Cat"), 8 | Wishlist(1, "Car"), 9 | Wishlist(2, "Home"), 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/model/repository/WishListRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.model.repository 2 | 3 | import com.example.model.dao.wishlist.WishlistDAOFacade 4 | import com.example.model.dao.wishlist.WishlistDAOFacadeImpl 5 | import com.example.model.entity.Wishlist 6 | 7 | class WishListRepository { 8 | private val dao: WishlistDAOFacade = WishlistDAOFacadeImpl() 9 | 10 | suspend fun allWishes(): List { 11 | val listOfWishes = dao.allWishes() 12 | if (listOfWishes.isEmpty()) { 13 | return listOf(Wishlist(-1, "Make a wish")) 14 | } 15 | return dao.allWishes() 16 | } 17 | 18 | suspend fun addWish(wish: String): Wishlist { 19 | return dao.addWish(wish) 20 | } 21 | 22 | suspend fun deleteWish(id: Int): Boolean { 23 | return dao.deleteWish(id) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/plugins/Routing.kt: -------------------------------------------------------------------------------- 1 | package com.example.plugins 2 | 3 | import com.example.controller.WishController 4 | import com.example.exceptions.DbElementInsertException 5 | import io.ktor.http.* 6 | import io.ktor.server.application.* 7 | import io.ktor.server.freemarker.* 8 | import io.ktor.server.plugins.statuspages.* 9 | import io.ktor.server.request.* 10 | import io.ktor.server.response.* 11 | import io.ktor.server.routing.* 12 | import io.ktor.server.util.* 13 | 14 | fun Application.configureRouting() { 15 | install(StatusPages) { 16 | exception { call, cause -> 17 | if (cause is DbElementInsertException) { 18 | call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError) 19 | } else { 20 | call.respondText(text = "400: $cause", status = HttpStatusCode.BadRequest) 21 | } 22 | } 23 | } 24 | routing { 25 | route("wish") { 26 | post("make") { 27 | val formWish = call.receiveParameters() 28 | val wishName = formWish.getOrFail("wish") 29 | WishController().addToWishList(wishName) 30 | call.respondRedirect("/wish/list") 31 | } 32 | get("list") { 33 | val wishList = WishController().getWishList() 34 | call.respond(FreeMarkerContent("wishlist.ftl", mapOf("wishList" to wishList))) 35 | } 36 | post("cancel") { 37 | val formId = call.receiveParameters() 38 | val wishId = formId.getOrFail("id").toInt() 39 | WishController().deleteFromWishList(wishId) 40 | call.respondRedirect("/wish/list") 41 | } 42 | get("topwishes") { 43 | val topWishList = WishController().getTopWishesExample() 44 | call.respond(FreeMarkerContent("topwishes.ftl", mapOf("topWishList" to topWishList))) 45 | } 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mvc-web/src/main/kotlin/com/example/plugins/Templating.kt: -------------------------------------------------------------------------------- 1 | package com.example.plugins 2 | 3 | import freemarker.cache.ClassTemplateLoader 4 | import freemarker.core.HTMLOutputFormat 5 | import io.ktor.server.application.* 6 | import io.ktor.server.freemarker.* 7 | 8 | fun Application.configureTemplating() { 9 | install(FreeMarker) { 10 | templateLoader = ClassTemplateLoader(this::class.java.classLoader, "view") 11 | outputFormat = HTMLOutputFormat.INSTANCE 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mvc-web/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | ktor: 2 | application: 3 | modules: 4 | - com.example.ApplicationKt.module 5 | datasource: 6 | url: "jdbc:h2:file:./build/db" 7 | driverClassName: "org.h2.Driver" 8 | deployment: 9 | port: 8080 -------------------------------------------------------------------------------- /mvc-web/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /mvc-web/src/main/resources/view/topwishes.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="wishList" type="com.example.model.repository.TopWishListExampleRepository" --> 2 | 3 | 4 | 5 | 6 | 7 | 8 | Topwishes 9 | 10 | 11 |
12 |
13 |

Top wishes

14 | <#list topWishList as topWish> 15 |
16 |

17 | 18 | ${topWish.id} 19 | 20 | 21 | ${topWish.wish} 22 | 23 |

24 |
25 | 26 |
27 |
28 | To wishlist 29 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /mvc-web/src/main/resources/view/wishlist.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="wishList" type="com.example.model.dao.wishlist.Wishlists" --> 2 | 3 | 4 | 5 | 6 | 7 | 8 | Wishlist 9 | 10 | 11 |
12 |
13 |

Wishlist

14 | <#list wishList as wish> 15 |
16 |

17 | 18 | ${wish.id} 19 | 20 | 21 | ${wish.wish} 22 | 23 |

24 |
25 | 26 |
27 |
28 |

Make a wish

29 |
30 |

31 | 34 |

35 |

36 | 37 |

38 |
39 |
40 |
41 |

Cancel a wish

42 |
43 |

44 | 47 |

48 |

49 | 50 |

51 |
52 |
53 |
54 | 55 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | bin/ 16 | !**/src/main/**/bin/ 17 | !**/src/test/**/bin/ 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | out/ 25 | !**/src/main/**/out/ 26 | !**/src/test/**/out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/README.md: -------------------------------------------------------------------------------- 1 | # Native image server using YAML configuration file 2 | 3 | This example shows how to build a native image for a Ktor application using YAML confinguration and [GraalVM](https://ktor.io/docs/graalvm.html). 4 | 5 | ## Build 6 | 7 | To build the application in native image, you need to execute the following command: 8 | 9 | ```bash 10 | ./gradlew nativeCompile 11 | ``` 12 | 13 | ## Run 14 | 15 | To run the application, you need to execute the following command: 16 | 17 | ```bash 18 | ./build/native/nativeCompile/com.example.ktor-sample 19 | ``` 20 | 21 | The application will be running at http://0.0.0.0:8080/. 22 | 23 | ## Update metadata 24 | 25 | If new dependencies are added to the project or a project has been modified, some classes may not be included in 26 | the native image. To fix this, you need to update the metadata file. 27 | 28 | First you need to run the application with the agent to generate the metadata file: 29 | 30 | ```bash 31 | ./gradlew -Pagent run 32 | ``` 33 | 34 | While the application is running, you need to access basic application functions 35 | 36 | It is important to gracefully shutdown the application to generate the metadata file. You can do this by using the [Shutdown URL](https://ktor.io/docs/server-shutdown-url.html): 37 | url: [http://localhost:8080/shutdown](http://localhost:8080/shutdown) 38 | 39 | Finally, you can copy to the metadata files: 40 | ```bash 41 | ./gradlew metadataCopy --task run --dir src/main/resources/META-INF/native-image 42 | ``` 43 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | val kotlin_version: String by project 3 | val logback_version: String by project 4 | 5 | plugins { 6 | kotlin("jvm") version "2.0.10" 7 | id("io.ktor.plugin") version "3.1.3" 8 | id("org.jetbrains.kotlin.plugin.serialization") version "2.0.10" 9 | id("org.graalvm.buildtools.native") version "0.10.2" 10 | } 11 | 12 | group = "example.com" 13 | version = "0.0.1" 14 | 15 | application { 16 | mainClass.set("io.ktor.server.netty.EngineMain") 17 | 18 | val isDevelopment: Boolean = project.ext.has("development") 19 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 20 | } 21 | 22 | repositories { 23 | mavenCentral() 24 | } 25 | 26 | dependencies { 27 | implementation("io.ktor:ktor-server-content-negotiation-jvm") 28 | implementation("io.ktor:ktor-server-core-jvm") 29 | implementation("io.ktor:ktor-serialization-kotlinx-json-jvm") 30 | implementation("io.ktor:ktor-server-netty-jvm") 31 | implementation("ch.qos.logback:logback-classic:$logback_version") 32 | implementation("io.ktor:ktor-server-config-yaml") 33 | testImplementation("io.ktor:ktor-server-test-host-jvm") 34 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 35 | } 36 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | ktor_version=3.1.3 3 | kotlin_version=2.0.10 4 | logback_version=1.4.14 5 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/native-image-server-with-yaml-config/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/native-image/jni-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name":"io.ktor.server.netty.EngineMain", 4 | "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] 5 | }, 6 | { 7 | "name":"java.lang.Boolean", 8 | "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] 9 | }, 10 | { 11 | "name":"java.lang.String", 12 | "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] 13 | }, 14 | { 15 | "name":"java.lang.System", 16 | "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] 17 | }, 18 | { 19 | "name":"sun.management.VMManagementImpl", 20 | "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/native-image/predefined-classes-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type":"agent-extracted", 4 | "classes":[ 5 | ] 6 | } 7 | ] 8 | 9 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/native-image/proxy-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/native-image/serialization-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "types":[ 3 | ], 4 | "lambdaCapturingTypes":[ 5 | ], 6 | "proxies":[ 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ktor-native-image-server-with-config" 2 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/kotlin/example/com/Application.kt: -------------------------------------------------------------------------------- 1 | package example.com 2 | 3 | import example.com.plugins.* 4 | import io.ktor.server.application.* 5 | 6 | fun main(args: Array) { 7 | io.ktor.server.netty.EngineMain.main(args) 8 | } 9 | 10 | fun Application.module() { 11 | configureSerialization() 12 | configureRouting() 13 | } 14 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/kotlin/example/com/plugins/Routing.kt: -------------------------------------------------------------------------------- 1 | package example.com.plugins 2 | 3 | import io.ktor.server.application.* 4 | import io.ktor.server.engine.* 5 | import io.ktor.server.response.* 6 | import io.ktor.server.routing.* 7 | 8 | fun Application.configureRouting() { 9 | routing { 10 | get("/") { 11 | call.respondText("Hello World!") 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/kotlin/example/com/plugins/Serialization.kt: -------------------------------------------------------------------------------- 1 | package example.com.plugins 2 | 3 | import io.ktor.serialization.kotlinx.json.* 4 | import io.ktor.server.application.* 5 | import io.ktor.server.plugins.contentnegotiation.* 6 | import io.ktor.server.response.* 7 | import io.ktor.server.routing.* 8 | 9 | fun Application.configureSerialization() { 10 | install(ContentNegotiation) { 11 | json() 12 | } 13 | routing { 14 | get("/json/kotlinx-serialization") { 15 | call.respond(mapOf("hello" to "world")) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/resources/META-INF/native-image/jni-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name":"io.ktor.server.netty.EngineMain", 4 | "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] 5 | }, 6 | { 7 | "name":"java.lang.Boolean", 8 | "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] 9 | }, 10 | { 11 | "name":"java.lang.String", 12 | "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] 13 | }, 14 | { 15 | "name":"java.lang.System", 16 | "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] 17 | }, 18 | { 19 | "name":"sun.management.VMManagementImpl", 20 | "fields":[{"name":"compTimeMonitoringSupport"}, {"name":"currentThreadCpuTimeSupport"}, {"name":"objectMonitorUsageSupport"}, {"name":"otherThreadCpuTimeSupport"}, {"name":"remoteDiagnosticCommandsSupport"}, {"name":"synchronizerUsageSupport"}, {"name":"threadAllocatedMemorySupport"}, {"name":"threadContentionMonitoringSupport"}] 21 | } 22 | ] -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/resources/META-INF/native-image/predefined-classes-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type":"agent-extracted", 4 | "classes":[ 5 | ] 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/resources/META-INF/native-image/proxy-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/resources/META-INF/native-image/serialization-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "types":[ 3 | ], 4 | "lambdaCapturingTypes":[ 5 | ], 6 | "proxies":[ 7 | ] 8 | } -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | ktor: 2 | application: 3 | modules: 4 | - example.com.ApplicationKt.module 5 | deployment: 6 | port: 8080 7 | shutdown: 8 | url: "/shutdown" 9 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /native-image-server-with-yaml-config/src/test/kotlin/example/com/ApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package example.com 2 | 3 | import example.com.plugins.* 4 | import io.ktor.client.request.* 5 | import io.ktor.client.statement.* 6 | import io.ktor.http.* 7 | import io.ktor.server.testing.* 8 | import kotlin.test.* 9 | 10 | class ApplicationTest { 11 | @Test 12 | fun testRoot() = testApplication { 13 | application { 14 | configureRouting() 15 | } 16 | client.get("/").apply { 17 | assertEquals(HttpStatusCode.OK, status) 18 | assertEquals("Hello World!", bodyAsText()) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /opentelemetry/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "OpenTelemetry-Ktor example" 2 | 3 | plugins { 4 | id("com.avast.gradle.docker-compose") version "0.17.1" 5 | } 6 | 7 | subprojects { 8 | group = "opentelemetry.ktor.example" 9 | version = "0.0.1" 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | } 15 | 16 | dockerCompose { 17 | useComposeFiles.add("docker/docker-compose.yml") 18 | } 19 | 20 | tasks.register("runWithDocker") { 21 | dependsOn("composeUp", ":server:run") 22 | } 23 | 24 | project(":server").setEnvironmentVariablesForOpenTelemetry() 25 | project(":client").setEnvironmentVariablesForOpenTelemetry() 26 | 27 | fun Project.setEnvironmentVariablesForOpenTelemetry() { 28 | tasks.withType { 29 | environment("OTEL_METRICS_EXPORTER", "none") 30 | environment("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317/") 31 | } 32 | } -------------------------------------------------------------------------------- /opentelemetry/client/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val ktor_version: String by project 2 | val logback_version: String by project 3 | val kotlin_version: String by project 4 | val opentelemetry_version: String by project 5 | 6 | plugins { 7 | kotlin("jvm") version "2.1.20" 8 | id("io.ktor.plugin") version "3.1.3" 9 | id("application") 10 | } 11 | 12 | application { 13 | mainClass.set("opentelemetry.ktor.example.ClientKt") 14 | 15 | val isDevelopment: Boolean = project.ext.has("development") 16 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 17 | } 18 | 19 | dependencies { 20 | implementation(project(":shared")) 21 | 22 | implementation("io.ktor:ktor-client-core-jvm") 23 | implementation("io.ktor:ktor-client-cio-jvm") 24 | implementation("io.ktor:ktor-client-websockets:$ktor_version") 25 | implementation("ch.qos.logback:logback-classic:$logback_version") 26 | 27 | implementation("io.opentelemetry.instrumentation:opentelemetry-ktor-3.0:$opentelemetry_version-alpha") 28 | } -------------------------------------------------------------------------------- /opentelemetry/client/src/main/kotlin/opentelemetry/ktor/example/Client.kt: -------------------------------------------------------------------------------- 1 | package opentelemetry.ktor.example 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.engine.cio.* 5 | import io.ktor.client.plugins.* 6 | import io.ktor.client.plugins.websocket.* 7 | import opentelemetry.ktor.example.plugins.opentelemetry.setupClientTelemetry 8 | 9 | suspend fun main() { 10 | val client = HttpClient(CIO) { 11 | install(WebSockets) 12 | 13 | defaultRequest { 14 | url("http://$SERVER_HOST:$SERVER_PORT") 15 | } 16 | 17 | setupClientTelemetry() 18 | } 19 | 20 | doRequests(client) 21 | } 22 | -------------------------------------------------------------------------------- /opentelemetry/client/src/main/kotlin/opentelemetry/ktor/example/Requests.kt: -------------------------------------------------------------------------------- 1 | package opentelemetry.ktor.example 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.plugins.websocket.* 5 | import io.ktor.client.request.* 6 | import io.ktor.websocket.* 7 | 8 | 9 | suspend fun doRequests(client: HttpClient) { 10 | // For this request you can see `CUSTOM` method instead of default `HTTP` in the Jaeger UI 11 | client.request("/known-methods") { 12 | method = CUSTOM_METHOD 13 | } 14 | 15 | // For this request you can't see `CUSTOM_NOT_KNOWN` method, you can see default `HTTP` in the Jaeger UI 16 | client.request("/known-methods") { 17 | method = CUSTOM_METHOD_NOT_KNOWN 18 | } 19 | 20 | // You can see tags `http.request.header.accept` and `http.response.header.content_type` for all requests 21 | // in the Jaeger UI and also `http.response.header.custom_header` for this request 22 | client.get("/captured-headers") 23 | 24 | // For this request you can see tag `error=true` and `Error` icon only for server trace in the Jaeger UI 25 | client.get("/span-status-extractor") 26 | 27 | // For this request you can see tag `span.kind=producer` only for server trace in the Jaeger UI 28 | client.post("/span-kind-extractor") 29 | 30 | // You can see attribute `start-time` and `end-time` in the Jaeger UI for all requests 31 | client.get("/attribute-extractor") 32 | 33 | // For this request you can see several spans and events only for server trace in the Jaeger UI 34 | client.get("/opentelemetry/tracer") 35 | 36 | // For this request you can see several events only for server trace in the Jaeger UI 37 | client.ws("/opentelemetry/websocket") { 38 | send(Frame.Text("Hello, world!")) 39 | repeat(10) { 40 | send(incoming.receive()) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /opentelemetry/client/src/main/kotlin/opentelemetry/ktor/example/plugins/opentelemetry/setupClientTelemetry.kt: -------------------------------------------------------------------------------- 1 | package opentelemetry.ktor.example.plugins.opentelemetry 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.engine.cio.* 5 | import io.ktor.http.* 6 | import io.opentelemetry.instrumentation.ktor.v2_0.common.internal.Experimental 7 | import io.opentelemetry.instrumentation.ktor.v3_0.KtorClientTelemetry 8 | import opentelemetry.ktor.example.CUSTOM_HEADER 9 | import opentelemetry.ktor.example.CUSTOM_METHOD 10 | import opentelemetry.ktor.example.getOpenTelemetry 11 | 12 | /** 13 | * Install OpenTelemetry on the client. 14 | * You can see usages of new extension functions for [KtorClientTelemetry]. 15 | */ 16 | fun HttpClientConfig.setupClientTelemetry() { 17 | val openTelemetry = getOpenTelemetry(serviceName = "opentelemetry-ktor-sample-client") 18 | install(KtorClientTelemetry) { 19 | setOpenTelemetry(openTelemetry) 20 | 21 | Experimental.emitExperimentalTelemetry(this) 22 | 23 | knownMethods(HttpMethod.DefaultMethods + CUSTOM_METHOD) 24 | capturedRequestHeaders(HttpHeaders.Accept) 25 | capturedResponseHeaders(HttpHeaders.ContentType, CUSTOM_HEADER) 26 | 27 | attributesExtractor { 28 | onStart { 29 | attributes.put("start-time", System.currentTimeMillis()) 30 | } 31 | onEnd { 32 | attributes.put("end-time", System.currentTimeMillis()) 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /opentelemetry/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # This file is used to start the Jaeger all-in-one container 2 | version: '3.7' 3 | services: 4 | jaeger: 5 | image: jaegertracing/all-in-one:latest 6 | ports: 7 | - "4317:4317" # OTLP gRPC receiver 8 | - "16686:16686" # Jaeger UI -------------------------------------------------------------------------------- /opentelemetry/gradle.properties: -------------------------------------------------------------------------------- 1 | ktor_version=3.1.3 2 | kotlin_version=2.1.20 3 | logback_version=1.5.12 4 | kotlin.code.style=official 5 | 6 | opentelemetry_version=2.14.0 7 | opentelemetry_semconv_version=1.30.0 8 | opentelemetry_exporter_otlp_version=1.48.0 9 | opentelemetry_sdk_extension_autoconfigure_version=1.48.0 -------------------------------------------------------------------------------- /opentelemetry/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/opentelemetry/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /opentelemetry/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /opentelemetry/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/opentelemetry/images/1.png -------------------------------------------------------------------------------- /opentelemetry/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/opentelemetry/images/2.png -------------------------------------------------------------------------------- /opentelemetry/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/opentelemetry/images/3.png -------------------------------------------------------------------------------- /opentelemetry/server/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val ktor_version: String by project 2 | val kotlin_version: String by project 3 | val opentelemetry_version: String by project 4 | val logback_version: String by project 5 | 6 | plugins { 7 | kotlin("jvm") version "2.1.20" 8 | id("io.ktor.plugin") version "3.1.3" 9 | id("application") 10 | } 11 | 12 | application { 13 | mainClass.set("opentelemetry.ktor.example.ServerKt") 14 | 15 | val isDevelopment: Boolean = project.ext.has("development") 16 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 17 | } 18 | 19 | dependencies { 20 | implementation(project(":shared")) 21 | 22 | implementation("io.ktor:ktor-server-cio-jvm") 23 | implementation("io.ktor:ktor-server-websockets:$ktor_version") 24 | implementation("ch.qos.logback:logback-classic:$logback_version") 25 | 26 | implementation("io.opentelemetry.instrumentation:opentelemetry-ktor-3.0:$opentelemetry_version-alpha") 27 | } -------------------------------------------------------------------------------- /opentelemetry/server/src/main/kotlin/opentelemetry/ktor/example/Server.kt: -------------------------------------------------------------------------------- 1 | package opentelemetry.ktor.example 2 | 3 | import io.ktor.server.application.* 4 | import io.ktor.server.cio.* 5 | import io.ktor.server.engine.* 6 | import opentelemetry.ktor.example.plugins.routing.configureRouting 7 | 8 | fun main() { 9 | embeddedServer(CIO, port = 8080, host = "0.0.0.0", module = Application::module) 10 | .start(wait = true) 11 | } 12 | 13 | fun Application.module() { 14 | configureRouting() 15 | } 16 | -------------------------------------------------------------------------------- /opentelemetry/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "opentelemetry-ktor-sample" 2 | 3 | include(":server") 4 | include(":client") 5 | include(":shared") -------------------------------------------------------------------------------- /opentelemetry/shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val opentelemetry_version: String by project 2 | val opentelemetry_semconv_version: String by project 3 | val opentelemetry_exporter_otlp_version: String by project 4 | val opentelemetry_sdk_extension_autoconfigure_version: String by project 5 | 6 | plugins { 7 | kotlin("jvm") version "2.1.20" 8 | id("io.ktor.plugin") version "3.1.3" 9 | } 10 | 11 | dependencies { 12 | implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:$opentelemetry_sdk_extension_autoconfigure_version"); 13 | implementation("io.opentelemetry:opentelemetry-exporter-otlp:$opentelemetry_exporter_otlp_version"); 14 | implementation("io.opentelemetry.semconv:opentelemetry-semconv:$opentelemetry_semconv_version") 15 | 16 | implementation("io.ktor:ktor-server-core-jvm") 17 | } -------------------------------------------------------------------------------- /opentelemetry/shared/src/main/kotlin/opentelemetry/ktor/example/constants.kt: -------------------------------------------------------------------------------- 1 | package opentelemetry.ktor.example 2 | 3 | import io.ktor.http.* 4 | 5 | const val SERVER_HOST = "0.0.0.0" 6 | const val SERVER_PORT = 8080 7 | 8 | // Custom HTTP methods 9 | val CUSTOM_METHOD = HttpMethod("CUSTOM") 10 | val CUSTOM_METHOD_NOT_KNOWN = HttpMethod("CUSTOM_NOT_KNOWN") 11 | 12 | // Custom HTTP headers 13 | const val CUSTOM_HEADER = "Custom-Header" 14 | -------------------------------------------------------------------------------- /opentelemetry/shared/src/main/kotlin/opentelemetry/ktor/example/utils.kt: -------------------------------------------------------------------------------- 1 | package opentelemetry.ktor.example 2 | 3 | import io.opentelemetry.api.OpenTelemetry 4 | import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk 5 | import io.opentelemetry.semconv.ServiceAttributes 6 | 7 | fun getOpenTelemetry(serviceName: String): OpenTelemetry { 8 | return AutoConfiguredOpenTelemetrySdk.builder().addResourceCustomizer { oldResource, _ -> 9 | oldResource.toBuilder() 10 | .putAll(oldResource.attributes) 11 | .put(ServiceAttributes.SERVICE_NAME, serviceName) 12 | .build() 13 | }.build().openTelemetrySdk 14 | } -------------------------------------------------------------------------------- /postgres/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | bin/ 16 | !**/src/main/**/bin/ 17 | !**/src/test/**/bin/ 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | out/ 25 | !**/src/main/**/out/ 26 | !**/src/test/**/out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ -------------------------------------------------------------------------------- /postgres/README.md: -------------------------------------------------------------------------------- 1 | # Postgres sample for Ktor Server 2 | 3 | An application for creating, editing and deleting articles that uses Postgres database running on Docker image as a storage. 4 | 5 | ## Steps 6 | 7 | 1. Execute gradle task `databaseInstance` and wait until Docker Compose builds image and starts container 8 | 9 | 2. Execute this command to run the sample: 10 | 11 | ```bash 12 | ./gradlew run 13 | ``` 14 | 15 | Then, you can open [http://localhost:8080/](http://localhost:8080/) in a browser to create, edit, and delete articles. -------------------------------------------------------------------------------- /postgres/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | id("org.jetbrains.kotlin.plugin.serialization") version "2.1.20" 8 | } 9 | 10 | group = "com.example" 11 | version = "0.0.1" 12 | application { 13 | mainClass.set("io.ktor.server.netty.EngineMain") 14 | 15 | val isDevelopment: Boolean = project.ext.has("development") 16 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | implementation("io.ktor:ktor-server-core-jvm") 25 | implementation("io.ktor:ktor-server-content-negotiation-jvm") 26 | implementation("io.ktor:ktor-serialization-kotlinx-json-jvm") 27 | implementation("io.ktor:ktor-server-netty-jvm") 28 | implementation("ch.qos.logback:logback-classic:$logback_version") 29 | implementation("io.ktor:ktor-server-config-yaml") 30 | implementation("org.postgresql:postgresql:42.5.1") 31 | testImplementation("io.ktor:ktor-server-test-host-jvm") 32 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 33 | } 34 | 35 | tasks.register("databaseInstance") { 36 | doLast { 37 | val command = arrayOf("docker-compose", "up") 38 | Runtime.getRuntime().exec(command) 39 | } 40 | } -------------------------------------------------------------------------------- /postgres/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | image: postgres:13.2-alpine 5 | volumes: 6 | - postgres:/var/lib/postgresql/data 7 | ports: 8 | - 5432:5432 9 | extends: 10 | file: postgres.yaml 11 | service: postgres 12 | volumes: 13 | postgres: -------------------------------------------------------------------------------- /postgres/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /postgres/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/postgres/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /postgres/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /postgres/postgres.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | postgres: 4 | environment: 5 | POSTGRES_USER: "user" 6 | POSTGRES_PASSWORD: "password" 7 | POSTGRES_DB: "articles" -------------------------------------------------------------------------------- /postgres/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ktor-postgres" -------------------------------------------------------------------------------- /postgres/src/main/kotlin/com/example/Application.kt: -------------------------------------------------------------------------------- 1 | package com.example 2 | 3 | import io.ktor.server.application.* 4 | import com.example.plugins.* 5 | 6 | fun main(args: Array): Unit = 7 | io.ktor.server.netty.EngineMain.main(args) 8 | 9 | @Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused. 10 | fun Application.module() { 11 | configureSerialization() 12 | configureRouting(dbConnection = connectToPostgres(embedded = false)) 13 | } 14 | -------------------------------------------------------------------------------- /postgres/src/main/kotlin/com/example/exceptions/DbElementInsertException.kt: -------------------------------------------------------------------------------- 1 | package com.example.exceptions 2 | 3 | class DbElementInsertException(message: String? = null, throwable: Throwable? = null) : Throwable(message, throwable) -------------------------------------------------------------------------------- /postgres/src/main/kotlin/com/example/exceptions/DbElementNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package com.example.exceptions 2 | 3 | class DbElementNotFoundException(message: String? = null, throwable: Throwable? = null) : Throwable(message, throwable) -------------------------------------------------------------------------------- /postgres/src/main/kotlin/com/example/models/Article.kt: -------------------------------------------------------------------------------- 1 | package com.example.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Article(val title: String, val body: String) 7 | -------------------------------------------------------------------------------- /postgres/src/main/kotlin/com/example/plugins/Serialization.kt: -------------------------------------------------------------------------------- 1 | package com.example.plugins 2 | 3 | import io.ktor.serialization.kotlinx.json.* 4 | import io.ktor.server.plugins.contentnegotiation.* 5 | import io.ktor.server.application.* 6 | 7 | fun Application.configureSerialization() { 8 | install(ContentNegotiation) { 9 | json() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /postgres/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | ktor: 2 | application: 3 | modules: 4 | - com.example.ApplicationKt.module 5 | deployment: 6 | port: 8080 7 | -------------------------------------------------------------------------------- /postgres/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /redirect-with-exception/README.md: -------------------------------------------------------------------------------- 1 | # Redirect With Exception 2 | 3 | This sample shows how to set up a redirect with when the request handling failed with an exception. 4 | 5 | 6 | ## Running 7 | 8 | Execute this command to run this sample: 9 | 10 | ```bash 11 | ./gradlew run 12 | ``` 13 | -------------------------------------------------------------------------------- /redirect-with-exception/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.samples.redirectwithexception.RedirectWithExceptionApplicationKt") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-server-html-builder") 19 | implementation("io.ktor:ktor-server-default-headers") 20 | implementation("io.ktor:ktor-server-status-pages") 21 | implementation("io.ktor:ktor-server-call-logging") 22 | implementation("io.ktor:ktor-server-netty-jvm") 23 | implementation("io.ktor:ktor-server-sessions-jvm") 24 | implementation("io.ktor:ktor-client-cio-jvm") 25 | implementation("ch.qos.logback:logback-classic:$logback_version") 26 | } -------------------------------------------------------------------------------- /redirect-with-exception/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /redirect-with-exception/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/redirect-with-exception/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /redirect-with-exception/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /reverse-proxy-ws/README.md: -------------------------------------------------------------------------------- 1 | # Reverse Proxy Application 2 | 3 | A reverse proxy application written using [WebSockets](https://ktor.io/docs/websocket.html). 4 | 5 | ## Running 6 | 7 | Execute this command to run this sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | -------------------------------------------------------------------------------- /reverse-proxy-ws/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.samples.reverseproxyws.ReverseProxyWsApplicationKt") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-server-netty-jvm") 19 | implementation("io.ktor:ktor-server-html-builder") 20 | implementation("io.ktor:ktor-server-websockets-jvm") 21 | implementation("io.ktor:ktor-client-cio-jvm") 22 | implementation("ch.qos.logback:logback-classic:$logback_version") 23 | } -------------------------------------------------------------------------------- /reverse-proxy-ws/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /reverse-proxy-ws/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/reverse-proxy-ws/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /reverse-proxy-ws/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /reverse-proxy/README.md: -------------------------------------------------------------------------------- 1 | # Reverse Proxy Application 2 | 3 | This sample shows how to write a reverse proxy application. 4 | 5 | ## Running 6 | 7 | Execute this command to run this sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | -------------------------------------------------------------------------------- /reverse-proxy/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.samples.reverseproxy.ReverseProxyApplicationKt") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-server-html-builder") 19 | implementation("ch.qos.logback:logback-classic:$logback_version") 20 | implementation("io.ktor:ktor-server-netty-jvm") 21 | implementation("io.ktor:ktor-client-cio-jvm") 22 | } -------------------------------------------------------------------------------- /reverse-proxy/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /reverse-proxy/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/reverse-proxy/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /reverse-proxy/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /rx/README.md: -------------------------------------------------------------------------------- 1 | # RxJava 2 | 3 | An application showing how to use RxJava with [Ktor](https://ktor.io). 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 14 | -------------------------------------------------------------------------------- /rx/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.samples.rx.RxApplicationKt") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-server-core-jvm") 19 | implementation("io.ktor:ktor-server-netty-jvm") 20 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.6.4") 21 | implementation("ch.qos.logback:logback-classic:$logback_version") 22 | testImplementation("io.ktor:ktor-server-test-host-jvm") 23 | } -------------------------------------------------------------------------------- /rx/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /rx/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/rx/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /rx/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /rx/src/main/kotlin/io/ktor/samples/rx/RxApplication.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.rx 2 | 3 | import io.ktor.http.* 4 | import io.ktor.server.application.* 5 | import io.ktor.server.engine.* 6 | import io.ktor.server.netty.* 7 | import io.ktor.server.response.* 8 | import io.ktor.server.routing.* 9 | import io.reactivex.* 10 | import kotlinx.coroutines.* 11 | import kotlinx.coroutines.reactive.* 12 | import java.util.concurrent.* 13 | 14 | /** 15 | * Documentation: https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive/kotlinx-coroutines-rx2 16 | */ 17 | fun main() { 18 | embeddedServer(Netty, port = 8080, module = Application::module).start(wait = true) 19 | } 20 | 21 | fun Application.module() { 22 | routing { 23 | get("/") { 24 | val result = Flowable.range(1, 10) 25 | .map { it * it } 26 | .delay(300L, TimeUnit.MILLISECONDS) 27 | .awaitLast() 28 | 29 | call.respondText("LAST ITEM: $result") 30 | } 31 | get("/iter") { 32 | call.respondTextWriter(ContentType.Text.Plain) { 33 | val writer = this 34 | Flowable.range(1, 10) 35 | .map { it * it } 36 | .delay(300L, TimeUnit.MILLISECONDS) 37 | .collect { 38 | writer.write("$it,") 39 | writer.flush() 40 | delay(100L) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sse/README.md: -------------------------------------------------------------------------------- 1 | # SSE 2 | 3 | A sample project showing how to use the SSE (Server-Sent Events) specification. 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the demo. 14 | -------------------------------------------------------------------------------- /sse/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.samples.sse.SseApplicationKt") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-server-core-jvm") 19 | implementation("io.ktor:ktor-server-netty-jvm") 20 | implementation("ch.qos.logback:logback-classic:$logback_version") 21 | } 22 | -------------------------------------------------------------------------------- /sse/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /sse/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/sse/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sse/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /structured-logging/README.md: -------------------------------------------------------------------------------- 1 | # Structured Logging 2 | 3 | An application showing how to do structured [logging](https://ktor.io/docs/logging.html). 4 | 5 | ## Running 6 | 7 | Execute this command to run the sample: 8 | 9 | ```bash 10 | ./gradlew run 11 | ``` 12 | 13 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) to see the sample home page. 14 | -------------------------------------------------------------------------------- /structured-logging/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | } 8 | 9 | application { 10 | mainClass.set("io.ktor.samples.structuredlogging.ApplicationKt") 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | maven { 16 | url = uri("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers/") 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation("io.ktor:ktor-server-html-builder") 22 | implementation("io.ktor:ktor-server-netty-jvm") 23 | implementation("org.jetbrains.kotlin-wrappers:kotlin-css:1.0.0-pre.519") 24 | implementation("ch.qos.logback:logback-classic:$logback_version") 25 | implementation("net.logstash.logback:logstash-logback-encoder:7.1.1") 26 | testImplementation("io.ktor:ktor-server-test-host-jvm") 27 | } -------------------------------------------------------------------------------- /structured-logging/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /structured-logging/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/structured-logging/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /structured-logging/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/Application.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.structuredlogging 2 | 3 | import io.ktor.server.application.* 4 | import io.ktor.server.engine.* 5 | import io.ktor.server.netty.* 6 | import io.ktor.server.response.* 7 | import io.ktor.server.routing.* 8 | import java.util.* 9 | 10 | /** 11 | * The main entrypoint of the application. Starts a Netty server on port 8080, 12 | * and applies the [module] that includes a custom logger supporting 13 | * structured logging by attaching named context objects to the call 14 | * and uses them when logging. It uses slf4j internally. 15 | * 16 | * After 0.9.5 once the CallId + CallLogging is included, this shouldn't be necessary and one could use MDC directly: 17 | * https://ktor.io/docs/call-id.html#put-call-id-mdc 18 | */ 19 | fun main() { 20 | embeddedServer(Netty, port = 8080, module = Application::module).start(true) 21 | } 22 | 23 | /** 24 | * This [module] registers an interceptor to the infrastructure pipeline 25 | * that attaches a [UUID] representing the requestId to the [logger] logger of the call. 26 | * 27 | * Whenever a log is performed, all the attached context objects performed by [StructuredLogger.attach], 28 | * will be associated to that log message. 29 | */ 30 | fun Application.module() { 31 | intercept(ApplicationCallPipeline.Plugins) { 32 | val requestId = UUID.randomUUID() 33 | call.logger.attach("req.Id", requestId.toString()) { 34 | call.logger.info("Interceptor[start]") 35 | proceed() 36 | call.logger.info("Interceptor[end]") 37 | } 38 | } 39 | routing { 40 | get("/") { 41 | call.logger.info("Respond[start]") 42 | call.respondText("HELLO WORLD") 43 | call.logger.info("Respond[end]") 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /structured-logging/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /version-diff/README.md: -------------------------------------------------------------------------------- 1 | # Version diff 2 | 3 | A diff tool for maven artifacts written using the [Ktor](https://ktor.io) client. 4 | 5 | ## Running 6 | 7 | Execute this command in a sample directory: 8 | 9 | ```bash 10 | ./gradlew run --args="1.6.8 2.2.2" 11 | ``` 12 | 13 | * `1.6.8` and `2.2.2` are the versions of the Maven artifact to compare. 14 | * By default, the repository URL is https://repo.maven.apache.org/maven2/io/ktor. 15 | If you want to use a repository other than the default, provide it explicitly as the third argument: 16 | ```bash 17 | ./gradlew run --args="2.3.13 3.0.0 https://oss.sonatype.org/content/repositories/releases/io/ktor" 18 | ``` -------------------------------------------------------------------------------- /version-diff/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | kotlin("plugin.serialization") version "2.1.20" 8 | } 9 | 10 | group = "org.example" 11 | version = "1.0-SNAPSHOT" 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | implementation("io.ktor:ktor-client-content-negotiation") 19 | implementation("io.ktor:ktor-serialization-kotlinx-xml") 20 | implementation("io.ktor:ktor-server-default-headers") 21 | implementation("io.ktor:ktor-server-auth") 22 | implementation("ch.qos.logback:logback-classic:$logback_version") 23 | implementation("io.ktor:ktor-server-core-jvm") 24 | implementation("io.ktor:ktor-client-cio-jvm") 25 | implementation("io.ktor:ktor-client-logging-jvm") 26 | testImplementation(kotlin("test")) 27 | } 28 | 29 | tasks.test { 30 | useJUnitPlatform() 31 | } 32 | 33 | kotlin { 34 | jvmToolchain(8) 35 | } 36 | 37 | application { 38 | mainClass.set("MainKt") 39 | } -------------------------------------------------------------------------------- /version-diff/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /version-diff/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/version-diff/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /version-diff/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /version-diff/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "version-diff" 3 | 4 | -------------------------------------------------------------------------------- /version-diff/src/main/kotlin/Utils.kt: -------------------------------------------------------------------------------- 1 | import io.ktor.client.* 2 | import io.ktor.client.call.body 3 | import io.ktor.client.call.body 4 | import io.ktor.client.request.* 5 | import io.ktor.client.statement.* 6 | 7 | private const val HREF_TOKEN = "href=\"" 8 | 9 | internal fun diffVersions(artifactInfo: List, from: String, to: String): VersionsDiff { 10 | val old = artifactInfo.filter { it.versions.contains(from) } 11 | val new = artifactInfo.filter { it.versions.contains(to) } 12 | 13 | val newArtifacts = new.filter { it !in old } 14 | val removedArtifacts = old.filter { it !in new } 15 | 16 | return VersionsDiff(newArtifacts, removedArtifacts) 17 | } 18 | 19 | internal suspend fun HttpClient.downloadArtifactVersions(repo: String, artifact: String): Set = 20 | get("$repo/$artifact").bodyAsText().extractHrefContent().toSet() 21 | 22 | internal suspend fun HttpClient.downloadArtifactNames(repo: String): List = 23 | get(repo).bodyAsText().extractHrefContent() 24 | 25 | internal fun String.extractHrefContent(): List { 26 | var current = 0 27 | val result = mutableListOf() 28 | while (true) { 29 | current = indexOf(HREF_TOKEN, current) 30 | if (current == -1) break 31 | 32 | current += HREF_TOKEN.length 33 | val end = indexOf("\"", current) 34 | 35 | val name = substring(current, end - 1) 36 | if (name != ".." && !name.startsWith("maven")) { 37 | result.add(name) 38 | } 39 | } 40 | 41 | return result 42 | } 43 | -------------------------------------------------------------------------------- /youkube/README.md: -------------------------------------------------------------------------------- 1 | # YouKube 2 | 3 | A video upload/view application written with [Ktor](https://ktor.io). 4 | This application uses the following plugins: 5 | - [Resources](https://ktor.io/docs/type-safe-routing.html) 6 | - [HTML DSL](https://ktor.io/docs/html-dsl.html) 7 | - [ConditionalHeaders](https://ktor.io/docs/conditional-headers.html) 8 | - [PartialContent](https://ktor.io/docs/partial-content.html) 9 | - [Compression](https://ktor.io/docs/compression.html) 10 | - [DefaultHeaders](https://ktor.io/docs/default-headers.html) 11 | - [CallLogging](https://ktor.io/docs/call-logging.html) 12 | 13 | 14 | ## Running 15 | 16 | Execute this command to run the sample: 17 | 18 | ```bash 19 | ./gradlew run 20 | ``` 21 | 22 | Then, navigate to [http://localhost:8080/](http://localhost:8080/) and use `root`/`root` to login. 23 | -------------------------------------------------------------------------------- /youkube/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val kotlin_version: String by project 2 | val logback_version: String by project 3 | 4 | plugins { 5 | kotlin("jvm") version "2.1.20" 6 | id("io.ktor.plugin") version "3.1.3" 7 | kotlin("plugin.serialization") version "2.1.20" 8 | } 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation("io.ktor:ktor-server-auth") 20 | implementation("io.ktor:ktor-server-resources") 21 | implementation("io.ktor:ktor-server-compression") 22 | implementation("io.ktor:ktor-server-conditional-headers") 23 | implementation("io.ktor:ktor-server-default-headers") 24 | implementation("io.ktor:ktor-server-partial-content") 25 | implementation("io.ktor:ktor-server-call-logging") 26 | implementation("io.ktor:ktor-server-html-builder") 27 | implementation("ch.qos.logback:logback-classic:$logback_version") 28 | implementation("org.ehcache:ehcache:3.9.7") 29 | implementation("com.google.code.gson:gson:2.9.1") 30 | implementation("io.ktor:ktor-server-netty-jvm") 31 | testImplementation("io.mockk:mockk:1.13.4") 32 | testImplementation("org.jetbrains.kotlin:kotlin-test") 33 | testImplementation("io.ktor:ktor-server-test-host-jvm") 34 | } -------------------------------------------------------------------------------- /youkube/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin_version=2.1.20 2 | logback_version=1.3.14 3 | kotlin.code.style=official 4 | -------------------------------------------------------------------------------- /youkube/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ktorio/ktor-samples/477179a902f994e3b175bf7cd3c7c7b72a66fb90/youkube/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /youkube/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /youkube/src/main/kotlin/io/ktor/samples/youkube/Styles.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.youkube 2 | 3 | import io.ktor.resources.* 4 | import io.ktor.server.application.* 5 | import io.ktor.server.http.content.* 6 | import io.ktor.server.resources.* 7 | import io.ktor.server.resources.get 8 | import io.ktor.server.response.* 9 | import io.ktor.server.routing.* 10 | import kotlinx.serialization.Serializable 11 | 12 | @Resource("/styles/main.css") 13 | class MainCss() 14 | 15 | /** 16 | * Register the styles, [MainCss] route (/styles/main.css) 17 | */ 18 | fun Route.styles() { 19 | /** 20 | * On a GET request to the [MainCss] route, it returns the `blog.css` file from the resources. 21 | * 22 | * Here we could preprocess or join several CSS/SASS/LESS. 23 | */ 24 | get { 25 | call.respond(call.resolveResource("blog.css")!!) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /youkube/src/main/kotlin/io/ktor/samples/youkube/Video.kt: -------------------------------------------------------------------------------- 1 | package io.ktor.samples.youkube 2 | 3 | import java.io.* 4 | 5 | /** 6 | * Entity representing a [Video]. 7 | * It includes a numeric [id], a [title] with the video title, an [authorId] with the user that uploaded the video 8 | * and a [videoFileName] where the video is stored. 9 | */ 10 | data class Video(val id: Long, val title: String, val authorId: String, val videoFileName: String) : Serializable 11 | -------------------------------------------------------------------------------- /youkube/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | port = ${?PORT} 5 | } 6 | 7 | application { 8 | modules = [ io.ktor.samples.youkube.YoukubeApplicationKt.main ] 9 | } 10 | } 11 | 12 | youkube { 13 | session { 14 | cookie { 15 | key = 03e156f6058a13813816065 16 | } 17 | } 18 | 19 | upload { 20 | dir = .youkube-video 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /youkube/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------