Skip to content

Commit

Permalink
Cleanup content package, merge HasVersions with Resource, remove unus…
Browse files Browse the repository at this point in the history
…ed Attributes
  • Loading branch information
orangy committed Oct 30, 2016
1 parent c58a4e0 commit 948bc46
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import org.jetbrains.ktor.response.*
import org.jetbrains.ktor.routing.*
import org.jetbrains.ktor.testing.*
import org.jetbrains.ktor.tests.*
import org.jetbrains.ktor.util.*
import org.junit.*
import java.time.*
import java.util.zip.*
Expand Down Expand Up @@ -319,8 +318,7 @@ class CompressionTest {
application.routing {
get("/") {
call.respond(object : Resource, FinalContent.ChannelContent() {
override val headers: ValuesMap
get() = super.headers
override val headers by lazy { super.headers }

override val contentType: ContentType
get() = ContentType.Text.Plain
Expand All @@ -332,8 +330,6 @@ class CompressionTest {

override val cacheControl = CacheControl.NoCache(CacheControlVisibility.PUBLIC)

override val attributes = Attributes()

override val contentLength = 4L

override fun channel() = "test".byteInputStream().asAsyncChannel()
Expand Down
47 changes: 47 additions & 0 deletions ktor-core/src/org/jetbrains/ktor/content/CacheControl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.jetbrains.ktor.content

import java.util.*

sealed class CacheControl(val visibility: CacheControlVisibility?) {
class NoCache(visibility: CacheControlVisibility?) : CacheControl(visibility) {
override fun toString() = if (visibility == null) {
"no-cache"
} else {
"no-cache, ${visibility.name.toLowerCase()}"
}
}

class NoStore(visibility: CacheControlVisibility?) : CacheControl(visibility) {
override fun toString() = if (visibility == null) {
"no-store"
} else {
"no-store, ${visibility.name.toLowerCase()}"
}
}

class MaxAge(val maxAgeSeconds: Int, val proxyMaxAgeSeconds: Int? = null, val mustRevalidate: Boolean = false, val proxyRevalidate: Boolean = false, visibility: CacheControlVisibility? = null) : CacheControl(visibility) {
override fun toString(): String {
val parts = ArrayList<String>(5)
parts.add("max-age=$maxAgeSeconds")
if (proxyMaxAgeSeconds != null) {
parts.add("s-maxage=$proxyMaxAgeSeconds")
}
if (mustRevalidate) {
parts.add("must-revalidate")
}
if (proxyRevalidate) {
parts.add("proxy-revalidate")
}
if (visibility != null) {
parts.add(visibility.name.toLowerCase())
}

return parts.joinToString(", ")
}
}
}

enum class CacheControlVisibility {
PUBLIC,
PRIVATE
}
2 changes: 0 additions & 2 deletions ktor-core/src/org/jetbrains/ktor/content/LocalFileContent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ class LocalFileContent(val file: File, override val contentType: ContentType = d
constructor(baseDir: File, vararg relativePath: String, contentType: ContentType = defaultContentType(relativePath.last().extension())) : this(baseDir.safeAppend(Paths.get("", *relativePath)), contentType)
constructor(baseDir: Path, relativePath: Path, contentType: ContentType = defaultContentType(relativePath.fileName.extension())) : this(baseDir.safeAppend(relativePath).toFile(), contentType)

override val attributes = Attributes()

override val contentLength: Long
get() = file.length()

Expand Down
19 changes: 9 additions & 10 deletions ktor-core/src/org/jetbrains/ktor/content/Resource.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,26 @@ import org.jetbrains.ktor.response.*
import org.jetbrains.ktor.util.*
import java.time.*

interface Resource : HasVersions {
interface Resource {
val contentType: ContentType
override val versions: List<Version>
val contentLength: Long?

val versions: List<Version>
val expires: LocalDateTime?
val cacheControl: CacheControl?
@Deprecated("Shouldn't it be somewhere else instead?")
val attributes: Attributes
val contentLength: Long?

override val headers: ValuesMap
val headers: ValuesMap
get() = ValuesMap.build(true) {
appendAll(super.headers)
contentType(contentType)
contentLength?.let { contentLength ->
contentLength(contentLength)
}
versions.forEach { it.appendHeadersTo(this) }
expires?.let { expires ->
expires(expires)
}
cacheControl?.let { cacheControl ->
cacheControl(cacheControl)
}
contentLength?.let { contentLength ->
contentLength(contentLength)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.jetbrains.ktor.content

import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.util.*
import org.jetbrains.ktor.util.Attributes
import java.io.*
import java.nio.file.*
import java.util.jar.*
Expand All @@ -16,17 +15,14 @@ class ResourceFileContent(val zipFile: File, val resourcePath: String, val class
require(!normalized.startsWith("..")) { "Bad resource relative path $resourcePath" }
}

override val attributes = Attributes()

override val versions: List<Version>
get() = listOf(LastModifiedVersion(Files.getLastModifiedTime(zipFile.toPath())))

override val contentLength: Long?
get() = JarFile(zipFile).use { it.getJarEntry(resourcePath)?.size }


override val headers: ValuesMap
get() = super.headers
override val headers by lazy { super.headers }

override fun stream() = classLoader.getResourceAsStream(normalized) ?: throw IOException("Resource $normalized not found")

Expand Down
7 changes: 2 additions & 5 deletions ktor-core/src/org/jetbrains/ktor/content/URIFileContent.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
package org.jetbrains.ktor.content

import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.util.*
import java.net.*

class URIFileContent(val uri: URI, override val contentType: ContentType = defaultContentType(uri.path.extension())): FinalContent.StreamContentProvider(), Resource {
constructor(url: URL, contentType: ContentType = defaultContentType(url.path.extension())) : this(url.toURI(), contentType)

override val headers: ValuesMap
get() = super.headers
override val headers by lazy { super.headers }

override fun stream() = uri.toURL().openStream()
override fun stream() = uri.toURL().openStream() // TODO: use http client

override val versions: List<Version>
get() = emptyList()

override val expires = null
override val cacheControl = null
override val attributes = Attributes()
override val contentLength = null
}

Expand Down
78 changes: 11 additions & 67 deletions ktor-core/src/org/jetbrains/ktor/content/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,97 +1,41 @@
package org.jetbrains.ktor.content

import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.response.*
import org.jetbrains.ktor.util.*
import java.nio.file.attribute.*
import java.time.*
import java.util.*

interface HasVersions {
val versions: List<Version>

val headers: ValuesMap
get() = ValuesMap.build(true) {
versions.forEach { it.render(this) }
}
}

interface Version {
fun render(response: ApplicationResponse)
fun render(builder: ValuesMapBuilder)
fun appendHeadersTo(builder: ValuesMapBuilder)
}

data class LastModifiedVersion(val lastModified: LocalDateTime) : Version {
constructor(lastModified: FileTime) : this(LocalDateTime.ofInstant(lastModified.toInstant(), ZoneId.systemDefault()))
constructor(lastModified: Date) : this(lastModified.toLocalDateTime())

override fun render(response: ApplicationResponse) {
response.lastModified(lastModified.atZone(ZoneOffset.UTC))
}

override fun render(builder: ValuesMapBuilder) {
override fun appendHeadersTo(builder: ValuesMapBuilder) {
builder.lastModified(lastModified.atZone(ZoneOffset.UTC))
}
}
data class EntityTagVersion(val etag: String) : Version {
override fun render(response: ApplicationResponse) {
response.etag(etag)
}

override fun render(builder: ValuesMapBuilder) {
data class EntityTagVersion(val etag: String) : Version {
override fun appendHeadersTo(builder: ValuesMapBuilder) {
builder.etag(etag)
}
}

enum class CacheControlVisibility {
PUBLIC,
PRIVATE
}

sealed class CacheControl(val visibility: CacheControlVisibility?) {
class NoCache(visibility: CacheControlVisibility?) : CacheControl(visibility) {
override fun toString() = if (visibility == null) {
"no-cache"
} else {
"no-cache, ${visibility.name.toLowerCase()}"
}
}
class NoStore(visibility: CacheControlVisibility?) : CacheControl(visibility) {
override fun toString() = if (visibility == null) {
"no-store"
} else {
"no-store, ${visibility.name.toLowerCase()}"
}
}
class MaxAge(val maxAgeSeconds: Int, val proxyMaxAgeSeconds: Int? = null, val mustRevalidate: Boolean = false, val proxyRevalidate: Boolean = false, visibility: CacheControlVisibility? = null) : CacheControl(visibility) {
override fun toString(): String {
val parts = ArrayList<String>(5)
parts.add("max-age=$maxAgeSeconds")
if (proxyMaxAgeSeconds != null) {
parts.add("s-maxage=$proxyMaxAgeSeconds")
}
if (mustRevalidate) {
parts.add("must-revalidate")
}
if (proxyRevalidate) {
parts.add("proxy-revalidate")
}
if (visibility != null) {
parts.add(visibility.name.toLowerCase())
}

return parts.joinToString(", ")
}
}
}

fun FinalContent.lastModifiedAndEtagVersions(): List<Version> {
if (this is HasVersions) {
if (this is Resource) {
return versions
}

val headers = headers
return headers.getAll(HttpHeaders.LastModified).orEmpty().map { LastModifiedVersion(LocalDateTime.parse(it, httpDateFormat)) } +
headers.getAll(HttpHeaders.ETag).orEmpty().map { EntityTagVersion(it) }
val lastModifiedHeaders = headers.getAll(HttpHeaders.LastModified) ?: emptyList()
val etagHeaders = headers.getAll(HttpHeaders.ETag) ?: emptyList()
val versions = ArrayList<Version>(lastModifiedHeaders.size + etagHeaders.size)
lastModifiedHeaders.mapTo(versions) { LastModifiedVersion(LocalDateTime.parse(it, httpDateFormat)) }
etagHeaders.mapTo(versions) { EntityTagVersion(it) }
return versions
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ConditionalHeaders {
call.response.pipeline.intercept(RespondPipeline.After) {
val message = subject.message
when (message) {
is HasVersions -> checkVersions(call, message.versions)
is Resource -> checkVersions(call, message.versions)
is FinalContent -> checkVersions(call, message.lastModifiedAndEtagVersions())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import freemarker.template.*
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.content.*
import org.jetbrains.ktor.content.Version
import org.jetbrains.ktor.features.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.transform.*
import org.jetbrains.ktor.util.*
Expand Down Expand Up @@ -46,10 +45,8 @@ class FreeMarker(val config: Configuration) {

override val expires = null
override val cacheControl = null
override val attributes = Attributes()
override val contentLength = null

override val headers: ValuesMap
get() = super.headers
override val headers by lazy { super.headers }
}
}
8 changes: 2 additions & 6 deletions ktor-samples/you-kube/src/you/kube/Page.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import kotlinx.html.*
import kotlinx.html.stream.*
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.sessions.*
import org.jetbrains.ktor.util.*
import java.io.*

fun ApplicationCall.respondHtml(versions: List<Version>, visibility: CacheControlVisibility, block: HTML.() -> Unit): Nothing {
Expand All @@ -31,7 +29,7 @@ fun ApplicationCall.respondDefaultHtml(versions: List<Version>, visibility: Cach
div("brand-title") { +title }
div("brand-tagline") {
if (session != null) {
+"${session.userId}"
+session.userId
}
}

Expand Down Expand Up @@ -67,11 +65,9 @@ class HtmlContent(override val versions: List<Version>, visibility: CacheControl
override val expires = null
override val cacheControl = CacheControl.MaxAge(3600 * 24 * 7, mustRevalidate = true, visibility = visibility, proxyMaxAgeSeconds = null, proxyRevalidate = false)

override val attributes = Attributes()
override val contentLength = null

override val headers: ValuesMap
get() = super.headers
override val headers by lazy { super.headers }

override fun stream(out: OutputStream) {
with(out.bufferedWriter()) {
Expand Down

0 comments on commit 948bc46

Please sign in to comment.