ktor - bkug.by
TRANSCRIPT
KtorRuslan Ibragimov / ibragimov.by
Ktor
2/85
Конкуренты
3/85
VS
Конкуренты
4/85
VS
Можно использовать с Kotlin
5/85
Можно использовать с Kotlin
6/85
Асинхронные
7/85
Асинхронные
8/85
Корутины
9/85
Корутины
10/85
Конкуренты
11/85
VS
12/85
One more thing...
Мультиплатформа
13/85
API
Java, Java, Java-Java Jing-Jing-Jing
14/85
API
Hello, World!
15/85
Gradle
16/85
buildscript { repositories { jcenter() }
dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") }}
Gradle
17/85
apply plugin: "kotlin"
compileKotlin { kotlinOptions.jvmTarget = "1.8"}
compileTestKotlin { kotlinOptions.jvmTarget = "1.8"}
kotlin.experimental.coroutines = "enable"
Gradle
18/85
repositories { jcenter() maven { url "https://dl.bintray.com/kotlin/kotlinx" } maven { url "https://dl.bintray.com/kotlin/ktor" }}
dependencies { compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") compile("io.ktor:ktor-server-netty:$ktorVersion")}
Mainfun main(args: Array<String>) { embeddedServer(Netty, 8080) { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
19/85
Mainfun main(args: Array<String>) { embeddedServer(Netty, 8080) { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
20/85
Mainfun main(args: Array<String>) { embeddedServer(Netty, 8080) { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
21/85
Mainfun main(args: Array<String>) { embeddedServer(Netty, 8080) { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
22/85
Mainfun main(args: Array<String>) { embeddedServer(Netty, 8080) { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
23/85
Mainfun main(args: Array<String>) { embeddedServer(Netty, 8080) { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
24/85
Application::classfun main(args: Array<String>) { embeddedServer(Netty, 8080) { this: Application routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
25/85
Application::classfun main(args: Array<String>) { embeddedServer(Netty, 8080) { this: Application this.routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
26/85
Application::classApplication это Pipeline (точнее несколько Pipeline'ов)
27/85
Application::classApplication это Pipeline (точнее несколько Pipeline'ов)
28/85
ApplicationCall ApplicationCall
Pipeline
CheckHeaders
SetStatus
SerializeObject
Application::classApplication это Pipeline (точнее несколько Pipeline'ов)
29/85
ApplicationCall ApplicationCall
Pipeline
CheckHeaders
SetStatus
SerializeObject
Interceptors
PipelinePhase
30/85
ApplicationCall ApplicationCall
Application : ApplicationCallPipeline
Infrastructure Call Fallback
Netty ChannelPipeline
31/85
Servlet
32/85
PipelinesApplicationCallPipeline
● ApplicationReceivePipeline
● ApplicationSendPipeline
33/85
ApplicationCallPipeline (ApplicationCall)
● ApplicationReceivePipeline (ApplicationReceiveRequest, ApplicationCall, IncomingContent)
● ApplicationSendPipeline (ApplicationCall, OutgoingContent)
34/85
Pipelines
Application::classfun main(args: Array<String>) { embeddedServer(Netty, 8080) { this: Application this.routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
35/85
Application.interceptfun main(args: Array<String>) { embeddedServer(Netty, 8081) { intercept(ApplicationCallPipeline.Infrastructure) { // log request headers call.request.headers .forEach { name, values -> println("$name: ${values.joinToString()}") } }
//... }.start(wait = true)}
36/85
Application.interceptfun main(args: Array<String>) { embeddedServer(Netty, 8081) { intercept(ApplicationCallPipeline.Infrastructure) { // log request headers call.request.headers .forEach { name, values -> println("$name: ${values.joinToString()}") } }
//... }.start(wait = true)}
37/85
Application.interceptfun main(args: Array<String>) { embeddedServer(Netty, 8081) { intercept(ApplicationCallPipeline.Infrastructure) { // log request headers call.request.headers .forEach { name, values -> println("$name: ${values.joinToString()}") } }
//... }.start(wait = true)}
38/85
ApplicationCall::classfun main(args: Array<String>) { embeddedServer(Netty, 8081) { intercept(ApplicationCallPipeline.Infrastructure) { // log request headers call.request.headers .forEach { name, values -> println("$name: ${values.joinToString()}") } }
//... }.start(wait = true)}
39/85
ApplicationCall::classApplicationCall
● ApplicationRequest● ApplicationResponse● Attributes
40/85
Application
41/85
ApplicationCall ApplicationCall
ApplicationCallPipeline
Infrastructure Call Fallback
Feature● Роутинг● Аунтентификация● Логирование запросов● Проставление заголовков● CORS● Метрики● Сессии● и т.д.● см. ApplicationFeature
42/85
Featurerouting { get("/") { call.respondText("I am Groot!"), ContentType.Text.Html) }}
43/85
Featureinstall(Routing) { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) }}
44/85
Featurefun main(args: Array<String>) { embeddedServer(Netty, 8081) { install(DefaultHeaders) install(CallLogging)
//.. }.start(wait = true)}
45/85
Featuresintercept(ApplicationCallPipeline.Infrastructure) { // log request headers call.request.headers .forEach { name, values -> println("$name: ${values.joinToString()}") }}
46/85
Featuresclass HeaderLoggingFeature(configuration: Configuration) { val exclusions = configuration.exclusions
class Configuration { var exclusions: List<String> = listOf() }
fun log(call: ApplicationCall) { call.request.headers .filter { name, _ -> !exclusions.contains(name) } .forEach { name, values -> println("$name: ${values.joinToString()}") } }
companion object Feature : ApplicationFeature<ApplicationCallPipeline, HeaderLoggingFeature.Configuration, HeaderLoggingFeature> { override val key = AttributeKey<HeaderLoggingFeature>("HeaderLoggingFeature")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): HeaderLoggingFeature { val configuration = HeaderLoggingFeature.Configuration().apply(configure) val feature = HeaderLoggingFeature(configuration)
pipeline.intercept(ApplicationCallPipeline.Infrastructure) { feature.log(call) } return feature } }}
47/85
Featuresclass HeaderLoggingFeature(configuration: Configuration) { val exclusions = configuration.exclusions
class Configuration { var exclusions: List<String> = listOf() }
fun log(call: ApplicationCall) { call.request.headers .filter { name, _ -> !exclusions.contains(name) } .forEach { name, values -> println("$name: ${values.joinToString()}") } }
companion object Feature : ApplicationFeature<ApplicationCallPipeline, HeaderLoggingFeature.Configuration, HeaderLoggingFeature> { override val key = AttributeKey<HeaderLoggingFeature>("HeaderLoggingFeature")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): HeaderLoggingFeature { val configuration = HeaderLoggingFeature.Configuration().apply(configure) val feature = HeaderLoggingFeature(configuration)
pipeline.intercept(ApplicationCallPipeline.Infrastructure) { feature.log(call) } return feature } }} 48/85
Featuresclass HeaderLoggingFeature(configuration: Configuration) { val exclusions = configuration.exclusions
class Configuration { var exclusions: List<String> = listOf() }
fun log(call: ApplicationCall) { call.request.headers .filter { name, _ -> !exclusions.contains(name) } .forEach { name, values -> println("$name: ${values.joinToString()}") } }
companion object Feature : ApplicationFeature<ApplicationCallPipeline, HeaderLoggingFeature.Configuration, HeaderLoggingFeature> { override val key = AttributeKey<HeaderLoggingFeature>("HeaderLoggingFeature")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): HeaderLoggingFeature { val configuration = HeaderLoggingFeature.Configuration().apply(configure) val feature = HeaderLoggingFeature(configuration)
pipeline.intercept(ApplicationCallPipeline.Infrastructure) { feature.log(call) } return feature } }} 49/85
Featuresclass HeaderLoggingFeature(configuration: Configuration) { val exclusions = configuration.exclusions
class Configuration { var exclusions: List<String> = listOf() }
fun log(call: ApplicationCall) { call.request.headers .filter { name, _ -> !exclusions.contains(name) } .forEach { name, values -> println("$name: ${values.joinToString()}") } }
companion object Feature : ApplicationFeature<ApplicationCallPipeline, HeaderLoggingFeature.Configuration, HeaderLoggingFeature> { override val key = AttributeKey<HeaderLoggingFeature>("HeaderLoggingFeature")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): HeaderLoggingFeature { val configuration = HeaderLoggingFeature.Configuration().apply(configure) val feature = HeaderLoggingFeature(configuration)
pipeline.intercept(ApplicationCallPipeline.Infrastructure) { feature.log(call) } return feature } }} 50/85
Featuresclass HeaderLoggingFeature(configuration: Configuration) { val exclusions = configuration.exclusions
class Configuration { var exclusions: List<String> = listOf() }
fun log(call: ApplicationCall) { call.request.headers .filter { name, _ -> !exclusions.contains(name) } .forEach { name, values -> println("$name: ${values.joinToString()}") } }
companion object Feature : ApplicationFeature<ApplicationCallPipeline, HeaderLoggingFeature.Configuration, HeaderLoggingFeature> { override val key = AttributeKey<HeaderLoggingFeature>("HeaderLoggingFeature")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): HeaderLoggingFeature {
val configuration = HeaderLoggingFeature.Configuration().apply(configure) val feature = HeaderLoggingFeature(configuration)
pipeline.intercept(ApplicationCallPipeline.Infrastructure) { feature.log(call) } return feature } }}
51/85
Featuresclass HeaderLoggingFeature(configuration: Configuration) { val exclusions = configuration.exclusions
class Configuration { var exclusions: List<String> = listOf() }
fun log(call: ApplicationCall) { call.request.headers .filter { name, _ -> !exclusions.contains(name) } .forEach { name, values -> println("$name: ${values.joinToString()}") } }
companion object Feature : ApplicationFeature<ApplicationCallPipeline, HeaderLoggingFeature.Configuration, HeaderLoggingFeature> { override val key = AttributeKey<HeaderLoggingFeature>("HeaderLoggingFeature")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): HeaderLoggingFeature {
val configuration = HeaderLoggingFeature.Configuration().apply(configure) val feature = HeaderLoggingFeature(configuration)
pipeline.intercept(ApplicationCallPipeline.Infrastructure) { feature.log(call) } return feature } }}
52/85
Featuresclass HeaderLoggingFeature(configuration: Configuration) { val exclusions = configuration.exclusions
class Configuration { var exclusions: List<String> = listOf() }
fun log(call: ApplicationCall) { call.request.headers .filter { name, _ -> !exclusions.contains(name) } .forEach { name, values -> println("$name: ${values.joinToString()}") } } companion object Feature : ApplicationFeature<ApplicationCallPipeline, HeaderLoggingFeature.Configuration, HeaderLoggingFeature> { override val key = AttributeKey<HeaderLoggingFeature>("HeaderLoggingFeature")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): HeaderLoggingFeature { val configuration = HeaderLoggingFeature.Configuration().apply(configure) val feature = HeaderLoggingFeature(configuration)
pipeline.intercept(ApplicationCallPipeline.Infrastructure) { feature.log(call) } return feature } }}
53/85
Featuresintercept(ApplicationCallPipeline.Infrastructure) { // log request headers call.request.headers .forEach { name, values -> println("$name: ${values.joinToString()}") }}
install(HeaderLoggingFeature)
install(HeaderLoggingFeature) { exclusions = listOf("User-Agent")}
54/85
Featuresintercept(ApplicationCallPipeline.Infrastructure) { // log request headers call.request.headers .forEach { name, values -> println("$name: ${values.joinToString()}") }}
install(HeaderLoggingFeature)
install(HeaderLoggingFeature) { exclusions = listOf("User-Agent")}
55/85
Featuresintercept(ApplicationCallPipeline.Infrastructure) { // log request headers call.request.headers .forEach { name, values -> println("$name: ${values.joinToString()}") }}
install(HeaderLoggingFeature)
install(HeaderLoggingFeature) { exclusions = listOf("User-Agent")}
56/85
Application и организация кодаfun main(args: Array<String>) { embeddedServer(Netty, 8080) { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
57/85
Application и организация кодаfun main(args: Array<String>) { embeddedServer(Netty, 8080) { this: Application this.routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } } }.start(wait = true)}
58/85
Application и организация кодаfun Application.myApp() { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } }}
fun main(args: Array<String>) { embeddedServer(Netty, 8081) { myApp() }.start(wait = true)}
59/85
Application и организация кодаfun Application.myApp() { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } }}
fun main(args: Array<String>) { embeddedServer(Netty, 8081) { myApp() }.start(wait = true)}
60/85
Application и организация кодаfun Application.myApp() { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } }}
fun main(args: Array<String>) { embeddedServer(Netty, 8081) { myApp() }.start(wait = true)}
61/85
Тестированиеdependencies { compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") compile("io.ktor:ktor-server-netty:$ktorVersion")
compile("ch.qos.logback:logback-classic:1.2.1")
testCompile("junit:junit:4.12") testCompile("io.ktor:ktor-server-test-host:$ktorVersion")}
62/85
Тестированиеclass AppKtTest { @Test fun testIsIAmGroot() { withTestApplication(Application::myApp) { with(handleRequest(HttpMethod.Get, "/")) { assertEquals(HttpStatusCode.OK, response.status()) assertEquals("I am Groot!", response.content) } } }}
63/85
Тестированиеclass AppKtTest { @Test fun testIsIAmGroot() { withTestApplication(Application::myApp) { with(handleRequest(HttpMethod.Get, "/")) { assertEquals(HttpStatusCode.OK, response.status()) assertEquals("I am Groot!", response.content) } } }}
64/85
Тестированиеclass AppKtTest { @Test fun testIsIAmGroot() { withTestApplication(Application::myApp) { with(handleRequest(HttpMethod.Get, "/")) { assertEquals(HttpStatusCode.OK, response.status()) assertEquals("I am Groot!", response.content) } } }}
65/85
Тестированиеclass AppKtTest { @Test fun testIsIAmGroot() { withTestApplication(Application::myApp) { with(handleRequest(HttpMethod.Get, "/")) { assertEquals(HttpStatusCode.OK, response.status()) assertEquals("I am Groot!", response.content) } } }}
66/85
Autoreloadfun Application.myApp() { routing { get("/") { call.respondText("I am Groot!", ContentType.Text.Html) } }}
67/85
Autoreloadktor { deployment { port = 8080 watch = [ ktor-bkug ] }
application { modules = [ by.bkug.autoreload.AutoreloadKt.module ] }}
68/85
Autoreloadio.ktor.server.netty.DevelopmentEngine
70/85
AutoreloadApplicationEngineEnvironmentReloading
71/85
AutoreloadDemo?
72/85
СерверыNetty (ktor-server-netty),
Jetty (ktor-server-jetty),
Tomcat (ktor-server-tomcat)
Servlet 3.0+ (ktor-server-servlet)
73/85
HTTP Клиент
74/85
КлиентыApache HTTP (ktor-client-apache),
Jetty (ktor-client-jetty)
75/85
Пример
76/85
fun Application.myApp() { val client = HttpClient(Apache)}
Пример
77/85
fun Application.myApp() { val client = HttpClient(Apache)
routing { get("/call") { val text = client.get<String>( host = "localhost", port = 8081, path = "/text" )
call.respondText(text) } }}
Пример
78/85
fun Application.myApp() { val client = HttpClient(Apache)
routing { get("/call") { val text = client.get<String>( host = "localhost", port = 8081, path = "/text" )
call.respondText(text) } }}
Пример
79/85
val client = HttpClient(Apache) { install(JsonFeature)}
Пример
80/85
val user = client.get<User>( host = "localhost", port = 8081, path = "/json")
Пример
81/85
val user = client.get<User>( host = "localhost", port = 8081, path = "/json")
Про что еще можно было бы рассказать● Продвинутый роутинг● JSON● Статические данные (HTML, JS, …)● ExceptionHandling● IoC (DI)● HTTP/2● WebSockets● Как добавить $server? (Undertow, …)
82/85
Резюме● Ktor - connected systems● Application - Pipelines● Pipeline - Interceptors● Interceptors - Feature● Авторелоад● Тестирование● Клиент
83/85
Вопросы?
● Ktor.io: ktor.io● Awesome Kotlin: kotlin.link● Belarus Kotlin User Group: bkug.by
85/85