Servers Kotlin Ryan Harter @rharter Ktor Ktor Easy to use, fun - - PowerPoint PPT Presentation

servers kotlin
SMART_READER_LITE
LIVE PREVIEW

Servers Kotlin Ryan Harter @rharter Ktor Ktor Easy to use, fun - - PowerPoint PPT Presentation

Servers Kotlin Ryan Harter @rharter Ktor Ktor Easy to use, fun and asynchronous. Ktor Easy to use, fun and asynchronous. Composable, DSL based web services in Kotlin Ktor Application Ktor Application Tomcat Servlet Netty Jetty


slide-1
SLIDE 1

Servers ❤ Kotlin

Ryan Harter @rharter

slide-2
SLIDE 2

Ktor

slide-3
SLIDE 3

Ktor

Easy to use, fun and asynchronous.

slide-4
SLIDE 4

Ktor

Easy to use, fun and asynchronous. Composable, DSL based web services in Kotlin

slide-5
SLIDE 5

Ktor

Application

slide-6
SLIDE 6

Ktor

Application Jetty Netty Tomcat Servlet

slide-7
SLIDE 7

Ktor

Application Feature Feature Feature Feature Servlet

slide-8
SLIDE 8

Ktor

Application Feature Feature Feature Feature Routing HTML Templates Serialization Authentication Servlet

slide-9
SLIDE 9

Ktor

slide-10
SLIDE 10

Ktor

Verify

slide-11
SLIDE 11

Ktor

Verify Admin

slide-12
SLIDE 12

call.respond("Hello World") post("/verify") { routing { fun Application.verify() { } } }

slide-13
SLIDE 13

call.respond("Hello World") post("/verify") { routing { fun Application.verify() { } } }

slide-14
SLIDE 14

call.respond("Hello World") post("/verify") { routing { fun Application.verify() { } } }

slide-15
SLIDE 15

call.respond("Hello World") post("/verify") { routing { fun Application.verify() { } } }

slide-16
SLIDE 16

call.respond("Hello World") post("/verify") { routing { fun Application.verify() { } } }

slide-17
SLIDE 17 → curl -X POST -d '' http://localhost:8080/verify
slide-18
SLIDE 18 → curl -X POST -d '' http://localhost:8080/verify Hello World
slide-19
SLIDE 19

Typed Responses

slide-20
SLIDE 20

call.respond("Hello World") post("/verify") { routing { fun Application.verify() { } } }

slide-21
SLIDE 21

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String)

slide-22
SLIDE 22 → curl -X POST -d '' http://localhost:8080/verify
slide-23
SLIDE 23 → curl -X POST -d '' http://localhost:8080/verify →
slide-24
SLIDE 24

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String)

slide-25
SLIDE 25

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(StatusPages)

slide-26
SLIDE 26

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(StatusPages) { } exception<Throwable> { e -> }

slide-27
SLIDE 27

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(StatusPages) { } exception<Throwable> { e -> call.respondText(e.localizedMessage, ContentType.Text.Plain, HttpStatusCode.InternalServerError) }

slide-28
SLIDE 28

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(StatusPages) { } exception<Throwable> { e -> call.respondText(e.localizedMessage, ContentType.Text.Plain, HttpStatusCode.InternalServerError) }

slide-29
SLIDE 29 → curl -X POST -d '' http://localhost:8080/verify
slide-30
SLIDE 30 → curl -X POST -d '' http://localhost:8080/verify Cannot transform this request's content to class com.ryanharter.example.Response
slide-31
SLIDE 31

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(StatusPages) { } exception<Throwable> { e -> call.respondText(e.localizedMessage, ContentType.Text.Plain, HttpStatusCode.InternalServerError) }

slide-32
SLIDE 32

install(StatusPages) { }1 call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) ...

slide-33
SLIDE 33

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation)

install(StatusPages) { }1 ...

slide-34
SLIDE 34

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation)

install(StatusPages) { }1 ...

{ }2

slide-35
SLIDE 35

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation)

install(StatusPages) { }1 ...

{ }2 moshi()

≈ ≈

slide-36
SLIDE 36

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation)

install(StatusPages) { }1 ...

{ }2 moshi()

≈ ≈

@JsonClass(generateAdapter = true)

slide-37
SLIDE 37

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 moshi() @JsonClass(generateAdapter = true)

slide-38
SLIDE 38 → curl -X POST -d '' http://localhost:8080/verify
slide-39
SLIDE 39 → curl -X POST -d '' http://localhost:8080/verify {"status":"OK"}
slide-40
SLIDE 40

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 moshi() @JsonClass(generateAdapter = true)

slide-41
SLIDE 41

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ...

slide-42
SLIDE 42

Typed Requests

slide-43
SLIDE 43
slide-44
SLIDE 44
slide-45
SLIDE 45

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ...

slide-46
SLIDE 46

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ...

≈ ≈ ≈ ≈

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b

slide-47
SLIDE 47

call.respond(Response(status = "OK")) post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ... @JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b

≈ ≈ ≈ bird

slide-48
SLIDE 48

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ...

≈ ≈ ≈ bird

slide-49
SLIDE 49

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ...

≈ ≈ ≈

val request = call.receive<Request>()

≈ ≈ bird

slide-50
SLIDE 50

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ...

≈ ≈ ≈

val request = call.receive<Request>()

≈ ≈

call.respond(request)

bird

slide-51
SLIDE 51

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ... val request = call.receive<Request>() call.respond(request)

slide-52
SLIDE 52
slide-53
SLIDE 53 → cat << EOF >> /tmp/request.json
slide-54
SLIDE 54 → cat << EOF >> /tmp/request.json > { > "userId": "rharter", > "packageName": "com.pixite.pigment", > "productId": "com.pixite.pigment.subscription.monthly_t", > "token": “fpljlfogiejllhkebmjkpndm.AO-Oy5r83Kzef5afyMfL0suZM11l76cp_WdnWgOz... > } > EOF →
slide-55
SLIDE 55
slide-56
SLIDE 56 → curl -X POST —H "Content-Type: application/json" -d @/tmp/request.json \ http://localhost:8080/verify
slide-57
SLIDE 57 → curl -X POST -H "Content-Type: application/json" -d @/tmp/request.json \ http://localhost:8080/verify {“userId”:”rharter","packageName":"com.pixite.pigment","productId":"com.pixite.pigment.subscription.monthly_t", "token":"fpljlfogiejllhkebmjkpndm.AO-J1Oy5r83Kzef5afyMfL0suZM11l76cp_WdnWgOz..."}
slide-58
SLIDE 58

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } data class Response(val status: String) install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ... val request = call.receive<Request>() call.respond(request)

slide-59
SLIDE 59

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ... val request = call.receive<Request>() call.respond(request) ???

slide-60
SLIDE 60

External Components

slide-61
SLIDE 61

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ... val request = call.receive<Request>() call.respond(request) ???

slide-62
SLIDE 62

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ... val request = call.receive<Request>() call.respond(request) // Find valid subscription in db or remotely

≈ ≈

slide-63
SLIDE 63

@JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 @JsonClass(generateAdapter = true) ... val request = call.receive<Request>() // Find valid subscription in db or remotely // Save to database

≈ ≈

slide-64
SLIDE 64

// Return subscription or 404 @JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>() // Find valid subscription in db or remotely // Save to database

≈ ≈

slide-65
SLIDE 65

interface Api { suspend fun findSubscription(userId: String, packageName: String, productId: String, token: String): Subscription? }

slide-66
SLIDE 66

class PlayStore(serviceAccountFile: InputStream) : Store { private val publisher: AndroidPublisher by lazy {...} suspend fun findSubscription(userId: String, packageName: String, productId: String, token: String): Subscription? = coroutineScope { val response = async { publisher.purchases() .subscriptions() .get(packageName, productId, token) .execute() } response.await().asSubscription(ownerId, token) } }

slide-67
SLIDE 67

class PlayStore(serviceAccountFile: InputStream) : Store { private val publisher: AndroidPublisher by lazy {...} suspend fun findSubscription(userId: String, packageName: String, productId: String, token: String): Subscription? = coroutineScope { val response = async { publisher.purchases() .subscriptions() .get(packageName, productId, token) .execute() } response.await().asSubscription(ownerId, token) } }

class top class bottom

slide-68
SLIDE 68

class PlayStore(serviceAccountFile: InputStream) : Store { private val publisher: AndroidPublisher by lazy {...} suspend fun findSubscription(userId: String, packageName: String, productId: String, token: String): Subscription? = coroutineScope { val response = async { publisher.purchases() .subscriptions() .get(packageName, productId, token) .execute() } response.await().asSubscription(ownerId, token) } }

class top class bottom

slide-69
SLIDE 69

interface Database { suspend fun subscription(subscriptionId: String): Subscription? suspend fun subscriptionByToken(token: String): Subscription? suspend fun subscriptionByUserId(userId: String): Subscription? suspend fun createSubscription(subscription: Subscription): Subscription }

slide-70
SLIDE 70

// Return subscription or 404 @JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, val token: String )b post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>() // Find valid subscription in db or remotely // Save to database

slide-71
SLIDE 71

// Return subscription or 404 @JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, val productId: String, post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>() // Find valid subscription in db or remotely // Save to database

≈ ≈ ≈

val api = TotallyRealApi()

≈ ≈ ≈

slide-72
SLIDE 72

// Return subscription or 404 @JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>() // Find valid subscription in db or remotely // Save to database

≈ ≈ ≈

val db = InMemoryDatabase() val api = TotallyRealApi()

≈ ≈ ≈

slide-73
SLIDE 73

// Return subscription or 404 @JsonClass(generateAdapter = true) data class Request( val userId: String, val packageName: String, post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>() // Save to database

≈ ≈ ≈

val db = InMemoryDatabase() val api = TotallyRealApi()

≈ ≈

val subscription = db.subscriptionByUserId(request.userId)

slide-74
SLIDE 74

// Return subscription or 404 @JsonClass(generateAdapter = true) data class Request( val userId: String, post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>()

≈ ≈ ≈

val db = InMemoryDatabase() val api = TotallyRealApi()

?: api.findSubscription(request.userId, request.packageName request.productId, request.token) val subscription = db.subscriptionByUserId(request.userId)

slide-75
SLIDE 75

?: api.findSubscription(request.userId, request.packageName request.productId, request.token) // Return subscription or 404 @JsonClass(generateAdapter = true) data class Request( post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>()

≈ ≈

val db = InMemoryDatabase() val api = TotallyRealApi()

?.also { db.createSubscription(it) } val subscription = db.subscriptionByUserId(request.userId)

slide-76
SLIDE 76

post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>()

≈ ≈

val db = InMemoryDatabase() val api = TotallyRealApi() val subscription = db.subscriptionByUserId(request.userId) if (subscription == null) { call.respond(HttpStatusCode.NotFound, "Subscription invalid.") } else { call.respond(subscription) } ?: api.findSubscription(request.userId, request.packageName request.productId, request.token) ?.also { db.createSubscription(it) }

slide-77
SLIDE 77 → curl -X POST -H "Content-Type: application/json" -d @valid.json http://localhost:8080/verify
slide-78
SLIDE 78 → curl -X POST -H "Content-Type: application/json" -d @valid.json http://localhost:8080/verify {“canceled":false,"expiryDate":"2018-10-12T04:35:21.000Z","id":"668245e4-b673-4258- ba7c-4b0367833e61","ownerId":"rharter","startDate":"2018-09-12T04:35:21.000Z","token":"token3"}
slide-79
SLIDE 79 → curl -X POST -H "Content-Type: application/json" -d @valid.json http://localhost:8080/verify {“canceled":false,"expiryDate":"2018-10-12T04:35:21.000Z","id":"668245e4-b673-4258- ba7c-4b0367833e61","ownerId":"rharter","startDate":"2018-09-12T04:35:21.000Z","token":"token3"} → → curl -X POST -H "Content-Type: application/json" -d @invalid.json http://localhost:8080/verify
slide-80
SLIDE 80 → curl -X POST -H "Content-Type: application/json" -d @valid.json http://localhost:8080/verify {“canceled":false,"expiryDate":"2018-10-12T04:35:21.000Z","id":"668245e4-b673-4258- ba7c-4b0367833e61","ownerId":"rharter","startDate":"2018-09-12T04:35:21.000Z","token":"token3"} → → curl -X POST -H "Content-Type: application/json" -d @invalid.json http://localhost:8080/verify Subscription invalid. →
slide-81
SLIDE 81

Templates

slide-82
SLIDE 82

post("/verify") { routing { fun Application.verify() { } } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... val request = call.receive<Request>() val db = InMemoryDatabase() val api = TotallyRealApi() val subscription = db.subscriptionByUserId(request.userId) if (subscription == null) { call.respond(HttpStatusCode.NotFound, "Subscription invalid.") } else { call.respond(subscription) } ?: api.findSubscription(request.userId, request.packageName request.productId, request.token) ?.also { db.createSubscription(it) }

slide-83
SLIDE 83

post("/verify") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... ... }3 ...

slide-84
SLIDE 84

get(“/subscriptions") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }4 ... post("/verify") { ... }3

slide-85
SLIDE 85

get(“/subscriptions") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }4 ... post("/verify") { ... }3 val subscriptions = db.subscriptions() call.respond(subscriptions)

slide-86
SLIDE 86

http://localhost:8080/subscriptions

[ { "id": "sub-1", "ownerId": "Bandalls", "token": "asdf98hn", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": true }, { "id": "sub-2", "ownerId": "Editussion", "token": "asdf092s", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": false }, { "id": "sub-3", "ownerId": "Liveltekah", "token": "gju0u0fe", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": false }, { "id": "sub-4", "ownerId": "Ortspoon", "token": "diiefh48", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": true }, { "id": "sub-5", "ownerId": "Reakefit", "token": "dg09uui2", http://localhost:8080/subs…
slide-87
SLIDE 87

get(“/subscriptions") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }4 ... post("/verify") { ... }3 val subscriptions = db.subscriptions() call.respond(subscriptions)

slide-88
SLIDE 88

install(FreeMarker) { get(“/subscriptions") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }4 ... post("/verify") { ... }3 val subscriptions = db.subscriptions() call.respond(subscriptions) }freemarker

slide-89
SLIDE 89

install(FreeMarker) { get(“/subscriptions") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }4 ... post("/verify") { ... }3 val subscriptions = db.subscriptions() call.respond(subscriptions) }freemarker templateLoader = ClassTemplateLoader( this@module.javaClass.classLoader, "templates" )

slide-90
SLIDE 90

install(FreeMarker) { get(“/subscriptions") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }4 ... post("/verify") { ... }3 val subscriptions = db.subscriptions() call.respond(subscriptions) }freemarker ...

slide-91
SLIDE 91

install(FreeMarker) { get(“/subscriptions") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }4 ... post("/verify") { ... }3 val subscriptions = db.subscriptions() call.respond( }freemarker ... subscriptions )respond

slide-92
SLIDE 92

install(FreeMarker) { get(“/subscriptions") { routing { fun Application.verify() { } } install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }4 ... post("/verify") { ... }3 val subscriptions = db.subscriptions() call.respond( }freemarker ... )respond FreeMarkerContent("subscriptions.ftl", mapOf("subscriptions" to )) subscriptions

slide-93
SLIDE 93

install(FreeMarker) { get(“/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... ... val subscriptions = db.subscriptions() call.respond( }freemarker ... FreeMarkerContent("subscriptions.ftl", mapOf("subscriptions" to )) subscriptions } } }4 post("/verify") { ... }3 )respond

slide-94
SLIDE 94

install(FreeMarker) { get(“/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... ... val subscriptions = db.subscriptions() call.respond( }freemarker ... FreeMarkerContent("subscriptions.ftl", mapOf("subscriptions" to )) subscriptions } } }4 post("/verify") { ... }3 )respond

slide-95
SLIDE 95 <html> <head> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css"> <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script> </head> <body> <div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header"> <header class="demo-header mdl-layout__header mdl-color--grey-100 mdl-color-text--grey-600"> <div class="mdl-layout__header-row"> <span class="mdl-layout-title">Home</span> <div class="mdl-layout-spacer"></div> <div class="mdl-textfield mdl-js-textfield mdl-textfield--expandable"> <label class="mdl-button mdl-js-button mdl-button--icon" for="search"> <i class="material-icons">search</i> </label> <div class="mdl-textfield__expandable-holder"> <input class="mdl-textfield__input" type="text" id="search"> <label class="mdl-textfield__label" for="search">Enter your query...</label> </div> </div> <button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon" id="hdrbtn"> <i class="material-icons">more_vert</i> </button> <ul class="mdl-menu mdl-js-menu mdl-js-ripple-effect mdl-menu--bottom-right" for="hdrbtn"> <li class="mdl-menu__item">About</li> <li class="mdl-menu__item">Contact</li> <li class="mdl-menu__item">Legal information</li> </ul> </div> </header> <main class="mdl-layout__content mdl-color--grey-100"> <div class="mdl-grid"> <table class="mdl-data-table mdl-js-data-table mdl-data-table--selectable mdl-color--white mdl-shadow--2dp mdl-cell mdl-cell--12-col"> <thead> <tr> <th class="mdl-data-table__cell--non-numeric">Owner ID</th> <th class="mdl-data-table__cell--non-numeric">Start Date</th> <th class="mdl-data-table__cell--non-numeric">Expiry Date</th> <th class="mdl-data-table__cell--non-numeric">Cancelled</th> </tr> </thead> <tbody> <#list subscriptions as subscription> <tr> <td class="mdl-data-table__cell--non-numeric">${subscription.ownerId}</td> <td class="mdl-data-table__cell--non-numeric">${subscription.startDate?date}</td> <td class="mdl-data-table__cell--non-numeric">${subscription.expiryDate?date}</td> <td class="mdl-data-table__cell--non-numeric"> ${subscription.canceled?string('yes', 'no')} </td> </tr> </#list> </tbody> </table> </div> </main> </div> </body> </html>
slide-96
SLIDE 96

class="mdl-data-table__cell--non-numeric">Expiry Date</th> class="mdl-data-table__cell--non-numeric">Cancelled</th> bscriptions as subscription> class="mdl-data-table__cell--non-numeric">${subscription.ownerId}</td> class="mdl-data-table__cell--non-numeric">${subscription.startDate?date}</td> class="mdl-data-table__cell--non-numeric">${subscription.expiryDate?date}</td> class="mdl-data-table__cell--non-numeric"> ${subscription.canceled?string('yes', 'no')} >

slide-97
SLIDE 97 [ { "id": "sub-1", "ownerId": "Bandalls", "token": "asdf98hn", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": true }, { "id": "sub-2", "ownerId": "Editussion", "token": "asdf092s", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": false }, { "id": "sub-3", "ownerId": "Liveltekah", "token": "gju0u0fe", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": false }, { "id": "sub-4", "ownerId": "Ortspoon", "token": "diiefh48", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": true }, { "id": "sub-5", "ownerId": "Reakefit", "token": "dg09uui2",

http://localhost:8080/subscriptions

http://localhost:8080/subs…
slide-98
SLIDE 98 [ { "id": "sub-1", "ownerId": "Bandalls", "token": "asdf98hn", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": true }, { "id": "sub-2", "ownerId": "Editussion", "token": "asdf092s", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": false }, { "id": "sub-3", "ownerId": "Liveltekah", "token": "gju0u0fe", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": false }, { "id": "sub-4", "ownerId": "Ortspoon", "token": "diiefh48", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": true }, { "id": "sub-5", "ownerId": "Reakefit", "token": "dg09uui2",

http://localhost:8080/subscriptions

http://localhost:8080/subs…
slide-99
SLIDE 99

http://localhost:8080/subscriptions

http://localhost:8080/subs…
slide-100
SLIDE 100

Authentication

slide-101
SLIDE 101

}routing install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... ... val subscriptions = db.subscriptions() call.respond( }freemarker ... FreeMarkerContent("subscriptions.ftl", mapOf("subscriptions" to )) subscriptions } }4 post("/verify") { ... }3 )respond

slide-102
SLIDE 102

}routing install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... } }4 post("/verify") { ... }3 ...

slide-103
SLIDE 103

}routing install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... } }4 post("/verify") { ... }3 ... install(Authentication)

s

slide-104
SLIDE 104

install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... } }4 post("/verify") { ... }3 ... install(Authentication) {auth }auth }basic basic(name = "userAuth") {basic }routing

s

slide-105
SLIDE 105

install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... } }4 post("/verify") { ... }3 ... install(Authentication) {auth }auth }basic realm = "Verifier" basic(name = "userAuth") {basic }routing

s

slide-106
SLIDE 106

}routing install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... } }4 post("/verify") { ... }3 ... install(Authentication)

s

{auth }auth }validate }basic validate { credentials -> realm = "Verifier" basic(name = "userAuth") {basic

slide-107
SLIDE 107

}routing install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... } }4 post("/verify") { ... }3 ... install(Authentication)

s

{auth }auth authService.authenticate(credentials.name, credentials.password) }validate }basic validate { credentials -> realm = "Verifier" basic(name = "userAuth") {basic

slide-108
SLIDE 108

}routing install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... } }4 post("/verify") { ... }3 ... install(Authentication)

s

{auth}auth ...

slide-109
SLIDE 109

}routing install(FreeMarker) { get("/subscriptions") { routing { fun Application.verify() { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... } }4 post("/verify") { ... }3 ... install(Authentication) {auth}auth ...

slide-110
SLIDE 110

get("/subscriptions") { fun Application.verify() { } } }4 post("/verify") { ... }3 ... authenticate("userAuth") { } install(FreeMarker) { routing { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... install(Authentication) {auth}auth ...

slide-111
SLIDE 111

fun Application.verify() { get("/subscriptions") { } } }4 post("/verify") { ... }3 ... authenticate("userAuth") { } install(FreeMarker) { routing { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... install(Authentication) {auth}auth ...

slide-112
SLIDE 112

fun Application.verify() { } } post("/verify") { ... }3 authenticate("userAuth") { } get("/subscriptions") { val subscriptions = db.subscriptions() call.respond( FreeMarkerContent("subscriptions.ftl", mapOf("subscriptions" to subscriptions }4 )respond ) ) install(FreeMarker) { routing { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... install(Authentication) {auth}auth ...

slide-113
SLIDE 113

fun Application.verify() { } } post("/verify") { ... }3 authenticate("userAuth") { } get("/subscriptions") { val subscriptions = db.subscriptions() call.respond( FreeMarkerContent("subscriptions.ftl", mapOf( subscriptions }4 )respond val user = call.authentication.principal

d

"subscriptions" to ) ) install(FreeMarker) { routing { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... install(Authentication) {auth}auth ...

slide-114
SLIDE 114

post("/verify") { ... }3 } val subscriptions = db.subscriptions() call.respond( FreeMarkerContent("subscriptions.ftl", mapOf( subscriptions }4 )respond val user = call.authentication.principal

d

) "subscriptions" to ) , "user" to user fun Application.verify() { authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... install(Authentication) {auth}auth ...

slide-115
SLIDE 115 [ { "id": "sub-1", "ownerId": "Bandalls", "token": "asdf98hn", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": true }, { "id": "sub-2", "ownerId": "Editussion", "token": "asdf092s", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": false }, { "id": "sub-3", "ownerId": "Liveltekah", "token": "gju0u0fe", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": false }, { "id": "sub-4", "ownerId": "Ortspoon", "token": "diiefh48", "startDate": "Sep 11, 2018 11:35:21 PM", "expiryDate": "Oct 11, 2018 11:35:21 PM", "canceled": true }, { "id": "sub-5", "ownerId": "Reakefit", "token": "dg09uui2",

http://localhost:8080/subscriptions

http://localhost:8080/subs…
slide-116
SLIDE 116

http://localhost:8080/subscriptions

http://localhost:8080/subs…

Sign in

http://localhost:8080 Username Password Sign in Cancel
slide-117
SLIDE 117

http://localhost:8080/subscriptions

http://localhost:8080/subs…

Sign in

http://localhost:8080 Username Password Sign in Cancel admin
slide-118
SLIDE 118

http://localhost:8080/subscriptions

http://localhost:8080/subs…

Ryan

slide-119
SLIDE 119

Forms

slide-120
SLIDE 120

http://localhost:8080/subscriptions

http://localhost:8080/subs…

Ryan

slide-121
SLIDE 121

http://localhost:8080/subscriptions

http://localhost:8080/subs…

Ryan

slide-122
SLIDE 122

Ryan

slide-123
SLIDE 123

} } post("/verify") { ... }3 } }4

d

fun Application.verify() { authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { install(ContentNegotiation) install(StatusPages) { }1 ... { }2 ... }freemarker ... install(Authentication) {auth}auth ... ...

slide-124
SLIDE 124

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") {

slide-125
SLIDE 125

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters()

slide-126
SLIDE 126

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] post("/subscriptions") {

end post

slide-127
SLIDE 127

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] ?: throw IllegalArgumentException("Missing parameter: id") post("/subscriptions") {

end post

slide-128
SLIDE 128

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] ?: throw IllegalArgumentException("Missing parameter: id") val action = parameters[“action"] ?: throw IllegalArgumentException("Missing parameter: action") post("/subscriptions") {

end post

slide-129
SLIDE 129

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] ?: throw IllegalArgumentException("Missing parameter: id") val action = parameters[“action"] ?: throw IllegalArgumentException("Missing parameter: action") when (action) { }when post("/subscriptions") { val parameters = call.receiveParameters() ids and such

end post

slide-130
SLIDE 130

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] ?: throw IllegalArgumentException("Missing parameter: id") val action = parameters[“action"] ?: throw IllegalArgumentException("Missing parameter: action") when (action) { }when "delete" -> db.deleteSubscription(id) post("/subscriptions") { val parameters = call.receiveParameters() ids and such

end post

when (action) {

end when

slide-131
SLIDE 131

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] ?: throw IllegalArgumentException("Missing parameter: id") val action = parameters[“action"] ?: throw IllegalArgumentException("Missing parameter: action") when (action) { }when "delete" -> db.deleteSubscription(id) "cancel" -> { } -> cancel post("/subscriptions") { val parameters = call.receiveParameters() ids and such

end post

when (action) { “delete” -> db.deleteSubscription(id)

end when

slide-132
SLIDE 132

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] ?: throw IllegalArgumentException("Missing parameter: id") val action = parameters[“action"] ?: throw IllegalArgumentException("Missing parameter: action") when (action) { }when "delete" -> db.deleteSubscription(id) "cancel" -> { db.subscription(id) } -> cancel post("/subscriptions") { val parameters = call.receiveParameters() ids and such

end post

when (action) { “delete” -> db.deleteSubscription(id)

end when

slide-133
SLIDE 133

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { }post get("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] ?: throw IllegalArgumentException("Missing parameter: id") val action = parameters[“action"] ?: throw IllegalArgumentException("Missing parameter: action") when (action) { }when "delete" -> db.deleteSubscription(id) "cancel" -> { db.subscription(id) db.putSubscription(it.copy(canceled = true)) }db.subscription(id)?.also } -> cancel ?.also { post("/subscriptions") { val parameters = call.receiveParameters() ids and such

end post

when (action) { “delete” -> db.deleteSubscription(id)

end when

slide-134
SLIDE 134

} post("/verify") { ... }3 } }4

d

authenticate("userAuth") { get("/subscriptions") { install(FreeMarker) { routing { }freemarker ... install(Authentication) {auth}auth ... ... post("/subscriptions") { val parameters = call.receiveParameters() val id = parameters[“id"] ?: throw IllegalArgumentException("Missing parameter: id") val action = parameters[“action"] ?: throw IllegalArgumentException("Missing parameter: action") when (action) { "delete" -> db.deleteSubscription(id) "cancel" -> { db.subscription(id)?.also { db.putSubscription(it.copy(canceled = true)) }db.subscription(id)?.also } -> cancel }when }post call.respondRedirect("/subscriptions") get("/subscriptions") { post("/subscriptions") { val parameters = call.receiveParameters() ids and such

end post

when (action) { “delete” -> db.deleteSubscription(id)

end when

cancel stuff

slide-135
SLIDE 135

class="mdl-data-table__cell--non-numeric">Cancelled</th> bscriptions as subscription> class="mdl-data-table__cell--non-numeric">${subscription.ownerId}</td> class="mdl-data-table__cell--non-numeric">${subscription.startDate?date}</td> class="mdl-data-table__cell--non-numeric">${subscription.expiryDate?date}</td> class="mdl-data-table__cell--non-numeric"> ${subscription.canceled?string('yes', 'no')} >

slide-136
SLIDE 136

> <td class="mdl-data-table__cell--non-numeric"> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="cancel"/> <button type="submit" title="Cancel" class=“…” <#if subscription.canceled>disabled</#if>> <i class="material-icons">block</i> </button> </form> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="delete"/> <button type="submit" title="Delete" class=“…”> <i class="material-icons">delete</i> </button> </form> </td>

td /td

slide-137
SLIDE 137

> <td class="mdl-data-table__cell--non-numeric"> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="cancel"/> <button type="submit" title="Cancel" class=“…” <#if subscription.canceled>disabled</#if>> <i class="material-icons">block</i> </button> </form> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="delete"/> <button type="submit" title="Delete" class=“…”> <i class="material-icons">delete</i> </button> </form> </td>

td /td start td End td

slide-138
SLIDE 138

> <td class="mdl-data-table__cell--non-numeric"> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="cancel"/> <button type="submit" title="Cancel" class=“…” <#if subscription.canceled>disabled</#if>> <i class="material-icons">block</i> </button> </form> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="delete"/> <button type="submit" title="Delete" class=“…”> <i class="material-icons">delete</i> </button> </form> </td>

td /td start td End td form content

slide-139
SLIDE 139

> <td class="mdl-data-table__cell--non-numeric"> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="cancel"/> <button type="submit" title="Cancel" class=“…” <#if subscription.canceled>disabled</#if>> <i class="material-icons">block</i> </button> </form> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="delete"/> <button type="submit" title="Delete" class=“…”> <i class="material-icons">delete</i> </button> </form> </td>

td /td start td End td formstart td /form td button

slide-140
SLIDE 140

> <td class="mdl-data-table__cell--non-numeric"> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="cancel"/> <button type="submit" title="Cancel" class=“…” <#if subscription.canceled>disabled</#if>> <i class="material-icons">block</i> </button> </form> <form method="post" action="subscriptions"> <input type="hidden" name="id" value="${subscription.id}"/> <input type="hidden" name="action" value="delete"/> <button type="submit" title="Delete" class=“…”> <i class="material-icons">delete</i> </button> </form> </td>

td /td start td End td formstart td /form td inputs

slide-141
SLIDE 141

http://localhost:8080/subscriptions

http://localhost:8080/subs…

Ryan

slide-142
SLIDE 142

http://localhost:8080/subscriptions

http://localhost:8080/subs…

Ryan

Cancel
slide-143
SLIDE 143

http://localhost:8080/subscriptions

http://localhost:8080/subs…

Ryan

slide-144
SLIDE 144

Ryan

slide-145
SLIDE 145

Ryan

slide-146
SLIDE 146

Ktor

  • Web

http://ktor.io

slide-147
SLIDE 147

Ktor

  • Web
  • Github

http://ktor.io ktorio / ktor

slide-148
SLIDE 148

Ktor

  • Web
  • Github
  • Slack

http://ktor.io ktorio / ktor kotlinlang #ktor

slide-149
SLIDE 149

Ktor

  • Web
  • Github
  • Slack
  • Me

http://ktor.io ktorio / ktor kotlinlang #ktor @rharter

slide-150
SLIDE 150

Ktor

  • Web
  • Github
  • Slack
  • Me
  • Slides

http://ktor.io ktorio / ktor kotlinlang #ktor @rharter https://ryans.link/ktor

slide-151
SLIDE 151

Servers ❤ Kotlin

Ryan Harter @rharter