servers kotlin
play

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


  1. → curl -X POST —H "Content-Type: application/json" -d @/tmp/request.json \ http://localhost:8080/verify

  2. → 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..."}

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

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

  5. External Components

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

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

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

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

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

  11. 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) } }

  12. class top 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() class bottom .get(packageName, productId, token) .execute() } response.await(). asSubscription (ownerId, token) } }

  13. class PlayStore(serviceAccountFile: InputStream) : Store { private val publisher : AndroidPublisher by lazy {...} class top 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 bottom

  14. 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 }

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

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

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

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

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

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

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

  22. → curl -X POST -H "Content-Type: application/json" -d @valid.json http://localhost:8080/verify

  23. → 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"}

  24. → 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

  25. → 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. →

  26. Templates

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

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

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

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

  31. http://localhost:8080/subs… 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",

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

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

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

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

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

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

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

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

  40. < 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 >

  41. 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' ) } >

  42. http://localhost:8080/subs… 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",

  43. http://localhost:8080/subs… 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",

  44. http://localhost:8080/subs… http://localhost:8080/subscriptions

  45. Authentication

Recommend


More recommend