Skip to content
This repository has been archived by the owner on Jul 13, 2021. It is now read-only.

Upgrade ktor from 0.3.0 to 0.9.0 #29

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .idea/runConfigurations/Backend____Jetty.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

compile "org.jetbrains.ktor:ktor-locations:$ktor_version"
compile "org.jetbrains.ktor:ktor-html-builder:$ktor_version"
compile "io.ktor:ktor-locations:$ktor_version"
compile "io.ktor:ktor-html-builder:$ktor_version"
compile "org.ehcache:ehcache:3.0.0.m4"

compile "org.jetbrains.squash:squash-h2:$squash_version"

testCompile("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
testCompile "org.jetbrains.ktor:ktor-test-host:$ktor_version"
testCompile "io.ktor:ktor-server-test-host:$ktor_version"
testCompile "org.jsoup:jsoup:1.9.1"

compile "org.jetbrains.ktor:ktor-jetty:$ktor_version"
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
compile "io.ktor:ktor-server-jetty:$ktor_version"
compile group: "io.ktor", name: "ktor-gson", version: "0.9.0"
}

sourceSets {
Expand All @@ -41,4 +41,4 @@ kotlin {
}
}

mainClassName = 'org.jetbrains.ktor.jetty.DevelopmentHost'
mainClassName = "io.ktor.server.netty.DevelopmentEngine"
34 changes: 15 additions & 19 deletions backend/src/org/jetbrains/demo/thinkter/Application.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package org.jetbrains.demo.thinkter

import com.google.gson.*
import org.jetbrains.demo.thinkter.dao.*
import org.jetbrains.demo.thinkter.model.*
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.content.*
import org.jetbrains.ktor.features.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.locations.*
import org.jetbrains.ktor.logging.*
import org.jetbrains.ktor.routing.*
import org.jetbrains.ktor.sessions.*
import org.jetbrains.ktor.transform.*
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.GsonConverter
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.response.respond
import io.ktor.routing.*
import io.ktor.sessions.*

data class Session(val userId: String)

Expand All @@ -28,17 +25,17 @@ fun Application.main() {
exception<NotImplementedError> { call.respond(HttpStatusCode.NotImplemented) }
}

withSessions<Session> {
withCookieByValue {
settings = SessionCookiesSettings(transformers = listOf(SessionCookieTransformerMessageAuthentication(hashKey)))
install(Sessions){
cookie<Session>("SESSION"){
transform(SessionTransportTransformerMessageAuthentication(hashKey))
}
}

transform.register<RpcData> {
TextContent(Gson().toJson(it), ContentType.Application.Json)
install(ContentNegotiation){
register(ContentType.Application.Json, GsonConverter())
}

routing {
install(Routing) {
index(storage)
postThought(storage, ::hash)
delete(storage, ::hash)
Expand All @@ -48,5 +45,4 @@ fun Application.main() {
login(storage, ::hash)
register(storage, ::hash)
}
}

}
2 changes: 1 addition & 1 deletion backend/src/org/jetbrains/demo/thinkter/ApplicationPage.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.jetbrains.demo.thinkter

import kotlinx.html.*
import org.jetbrains.ktor.html.*
import io.ktor.html.*

class ApplicationPage : Template<HTML> {
val caption = Placeholder<TITLE>()
Expand Down
30 changes: 20 additions & 10 deletions backend/src/org/jetbrains/demo/thinkter/Delete.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package org.jetbrains.demo.thinkter

import org.jetbrains.demo.thinkter.dao.*
import org.jetbrains.demo.thinkter.model.*
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.locations.*
import org.jetbrains.ktor.routing.*
import org.jetbrains.ktor.sessions.*
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.locations.get
import io.ktor.locations.post
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.sessions.get
import io.ktor.sessions.sessions
import io.ktor.util.ValuesMap
import org.jetbrains.demo.thinkter.dao.ThinkterStorage
import org.jetbrains.demo.thinkter.model.PostThoughtToken
import org.jetbrains.demo.thinkter.model.RpcData

fun Route.delete(dao: ThinkterStorage, hashFunction: (String) -> String) {
get<ThoughtDelete> {
val user = call.sessionOrNull<Session>()?.let { dao.user(it.userId) }
val user = call.sessions.get<Session>()?.let { dao.user(it.userId) }
val date = System.currentTimeMillis()

if (user == null) {
Expand All @@ -22,10 +28,14 @@ fun Route.delete(dao: ThinkterStorage, hashFunction: (String) -> String) {
}

post<ThoughtDelete> {
val user = call.sessionOrNull<Session>()?.let { dao.user(it.userId) }
val user = call.sessions.get<Session>()?.let { dao.user(it.userId) }
val thought = dao.getThought(it.id)

if (user == null || thought.userId != user.userId || !call.verifyCode(it.date, user, it.code, hashFunction)) {
val form = call.receive<ValuesMap>()
val date = form["date"]?.toLong() ?: -1
val code = form["code"] ?: ""

if (user == null || thought.userId != user.userId || !call.verifyCode(date, user, code, hashFunction)) {
call.respond(HttpStatusCode.Forbidden)
} else {
dao.deleteThought(it.id)
Expand Down
40 changes: 19 additions & 21 deletions backend/src/org/jetbrains/demo/thinkter/Index.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,35 @@ package org.jetbrains.demo.thinkter

import org.jetbrains.demo.thinkter.dao.*
import org.jetbrains.demo.thinkter.model.*
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.html.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.locations.*
import org.jetbrains.ktor.response.*
import org.jetbrains.ktor.routing.*
import org.jetbrains.ktor.sessions.*
import io.ktor.application.*
import io.ktor.html.*
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.request.contentType
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.sessions.*
import java.time.*

fun Route.index(storage: ThinkterStorage) {
contentType(ContentType.Text.Html) {
get<Index> {
call.respondHtmlTemplate(ApplicationPage()) {
caption { +"Thinkter" }
}
}
}
contentType(ContentType.Application.Json) {
get<Index> {
val user = call.sessionOrNull<Session>()?.let { storage.user(it.userId) }
val top = storage.top(10).map(storage::getThought)
val latest = storage.latest(10).map(storage::getThought)
get<Index> {
val user = call.sessions.get<Session>()?.let { storage.user(it.userId) }
val top = storage.top(10).map(storage::getThought)
val latest = storage.latest(10).map(storage::getThought)

call.response.pipeline.intercept(ApplicationResponsePipeline.After) {
if(call.request.contentType().match(ContentType.Application.Json)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request's content type for GET? I believe it makes no sense as GET requests has no body.
ETag should be always specified to get browser cache working

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to preserve the original behaviour, but I couldn't get the example to work without these changes. I could use some help on this one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely it doesn't work because of this change

-        override var headers: dynamic = json("Accept" to "application/json")
+        override var headers: dynamic = json("Content-Type" to contentType)

I'd say we actually need both Accept and Content-Type headers otherwise the server could reject it. Not sure what is the default Accept header value

call.response.pipeline.intercept(ApplicationSendPipeline.After) {
val etagString = user?.userId + "," + top.joinToString { it.id.toString() } + latest.joinToString { it.id.toString() }
call.response.etag(etagString)
}

call.respond(IndexResponse(top, latest))
}else{
call.respondHtmlTemplate(ApplicationPage()) {
caption { +"Thinkter" }
}
}
}
get<Poll> { poll ->
if (poll.lastTime.isBlank()) {
call.respond(PollResponse(System.currentTimeMillis(), "0"))
Expand All @@ -44,7 +43,6 @@ fun Route.index(storage: ThinkterStorage) {
call.respond(PollResponse(time, if (count == 10) "10+" else count.toString()))
}
}
}
}

private fun Thought.toEpochMilli() = LocalDateTime.parse(date).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
4 changes: 2 additions & 2 deletions backend/src/org/jetbrains/demo/thinkter/Locations.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.jetbrains.demo.thinkter

import org.jetbrains.ktor.locations.*
import io.ktor.locations.*


@location("/")
Expand All @@ -13,7 +13,7 @@ class Poll(val lastTime: String = "")
data class PostThought(val text: String = "", val date: Long = 0L, val code: String = "", val replyTo: Int? = null)

@location("/thought/{id}/delete")
data class ThoughtDelete(val id: Int, val date: Long, val code: String)
data class ThoughtDelete(val id: Int)

@location("/thought/{id}")
data class ViewThought(val id: Int)
Expand Down
31 changes: 19 additions & 12 deletions backend/src/org/jetbrains/demo/thinkter/Login.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,45 @@ package org.jetbrains.demo.thinkter

import org.jetbrains.demo.thinkter.dao.*
import org.jetbrains.demo.thinkter.model.*
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.locations.*
import org.jetbrains.ktor.routing.*
import org.jetbrains.ktor.sessions.*
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.sessions.*
import io.ktor.routing.Route
import io.ktor.util.ValuesMap

fun Route.login(dao: ThinkterStorage, hash: (String) -> String) {
get<Login> {
val user = call.sessionOrNull<Session>()?.let { dao.user(it.userId) }
val user = call.sessions.get<Session>()?.let { dao.user(it.userId) }
if (user == null) {
call.respond(HttpStatusCode.Forbidden)
} else {
call.respond(LoginResponse(user))
}
}
post<Login> {
val form = call.receive<ValuesMap>()
val userId = form["userId"] ?: ""
val password = form["password"] ?: ""

val login = when {
it.userId.length < 4 -> null
it.password.length < 6 -> null
!userNameValid(it.userId) -> null
else -> dao.user(it.userId, hash(it.password))
userId.length < 4 -> null
password.length < 6 -> null
!userNameValid(userId) -> null
else -> dao.user(userId, hash(password))
}

if (login == null) {
call.respond(LoginResponse(error = "Invalid username or password"))
} else {
call.session(Session(login.userId))
call.sessions.set(Session(login.userId))
call.respond(LoginResponse(login))
}
}
post<Logout> {
call.clearSession()
call.sessions.clear<Session>()
call.respond(HttpStatusCode.OK)
}
}
29 changes: 20 additions & 9 deletions backend/src/org/jetbrains/demo/thinkter/PostThought.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package org.jetbrains.demo.thinkter

import org.jetbrains.demo.thinkter.dao.*
import org.jetbrains.demo.thinkter.model.*
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.locations.*
import org.jetbrains.ktor.routing.*
import org.jetbrains.ktor.sessions.*
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.*
import io.ktor.sessions.*
import io.ktor.util.ValuesMap

fun Route.postThought(dao: ThinkterStorage, hashFunction: (String) -> String) {
get<PostThought> {
val user = call.sessionOrNull<Session>()?.let { dao.user(it.userId) }
val user = call.sessions.get<Session>()?.let { dao.user(it.userId) }

if (user == null) {
call.respond(HttpStatusCode.Forbidden)
Expand All @@ -20,12 +23,20 @@ fun Route.postThought(dao: ThinkterStorage, hashFunction: (String) -> String) {
call.respond(PostThoughtToken(user.userId, date, code))
}
}

post<PostThought> {
val user = call.sessionOrNull<Session>()?.let { dao.user(it.userId) }
if (user == null || !call.verifyCode(it.date, user, it.code, hashFunction)) {
val form = call.receive<ValuesMap>()
val userId = form["userId"] ?: ""
val date = form["date"]?.toLong() ?: -1
val code = form["code"] ?: ""
val text = form["text"] ?: ""
val replyTo = form["replyTo"]?.toInt()

val user = call.sessions.get<Session>()?.let { dao.user(it.userId) }
if (user == null || !call.verifyCode(date, user, code, hashFunction)) {
call.respond(HttpStatusCode.Forbidden)
} else {
val id = dao.createThought(user.userId, it.text, it.replyTo)
val id = dao.createThought(user.userId, text, replyTo)
call.respond(PostThoughtResult(dao.getThought(id)))
}
}
Expand Down