Не подтверждена Коммит 18222b4e создал по автору Rustam's avatar Rustam Зафиксировано автором GitHub
Просмотр файлов

KTOR-343 Fix exception in template can't be handled in StatusPages (#2516)

владелец 066d0c4f
......@@ -10,8 +10,7 @@ import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.response.*
import io.ktor.util.*
import io.ktor.util.cio.*
import io.ktor.utils.io.*
import java.io.*
/**
* Represents a content handled by [FreeMarker] feature.
......@@ -51,31 +50,14 @@ public class FreeMarker(private val config: Configuration) {
}
}
private fun process(content: FreeMarkerContent): FreeMarkerOutgoingContent {
return FreeMarkerOutgoingContent(
config.getTemplate(content.template),
content.model,
content.etag,
content.contentType
)
}
private fun process(content: FreeMarkerContent): OutgoingContent = with(content) {
val writer = StringWriter()
config.getTemplate(content.template).process(model, writer)
private class FreeMarkerOutgoingContent(
val template: Template,
val model: Any?,
etag: String?,
override val contentType: ContentType
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
channel.bufferedWriter(contentType.charset() ?: Charsets.UTF_8).use {
template.process(model, it)
}
}
init {
if (etag != null) {
versions += EntityTagVersion(etag)
}
val result = TextContent(text = writer.toString(), contentType)
if (etag != null) {
result.versions += EntityTagVersion(etag)
}
return result
}
}
......@@ -65,7 +65,9 @@ class FreeMarkerTest {
fun testCompression() {
withTestApplication {
application.setUpTestTemplates()
application.install(Compression)
application.install(Compression) {
gzip { minimumSize(10) }
}
application.install(ConditionalHeaders)
application.routing {
......@@ -139,6 +141,28 @@ class FreeMarkerTest {
}
}
@Test
fun testErrorInContent() {
withTestApplication {
application.setUpTestTemplates()
application.install(StatusPages) {
exception<Throwable> {
call.respond("Error: template exception")
}
}
application.install(ConditionalHeaders)
application.routing {
get("/") {
call.respond(FreeMarkerContent("test.ftl", null))
}
}
handleRequest(HttpMethod.Get, "/").response.let { response ->
assertEquals("Error: template exception", response.content)
}
}
}
private fun Application.setUpTestTemplates() {
val bax = "$"
......
......@@ -12,6 +12,7 @@ import io.ktor.response.*
import io.ktor.util.*
import io.ktor.util.cio.*
import io.ktor.utils.io.*
import java.io.*
/**
* Response content which could be used to respond [ApplicationCalls] like `call.respond(MustacheContent(...))
......@@ -57,39 +58,14 @@ public class Mustache(configuration: Configuration) {
}
}
private fun process(content: MustacheContent): MustacheOutgoingContent {
return MustacheOutgoingContent(
mustacheFactory.compile(content.template),
content.model,
content.etag,
content.contentType
)
}
private fun process(content: MustacheContent): OutgoingContent = with(content) {
val writer = StringWriter()
mustacheFactory.compile(content.template).execute(writer, model)
/**
* Content which is responded when Mustache templates are rendered.
*
* @param template the compiled [com.github.mustachejava.Mustache] template
* @param model the model provided into the template
* @param etag value for `E-Tag` header (optional)
* @param contentType response's content type which is set to `text/html;charset=utf-8` by default
*/
private class MustacheOutgoingContent(
val template: com.github.mustachejava.Mustache,
val model: Any?,
etag: String?,
override val contentType: ContentType
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
channel.bufferedWriter(contentType.charset() ?: Charsets.UTF_8).use {
template.execute(it, model)
}
}
init {
if (etag != null) {
versions += EntityTagVersion(etag)
}
val result = TextContent(text = writer.toString(), contentType)
if (etag != null) {
result.versions += EntityTagVersion(etag)
}
return result
}
}
......@@ -99,7 +99,9 @@ class MustacheTest {
fun `Render template compressed with GZIP`() {
withTestApplication {
application.setupMustache()
application.install(Compression)
application.install(Compression) {
gzip { minimumSize(10) }
}
application.install(ConditionalHeaders)
application.routing {
......
......@@ -13,6 +13,7 @@ import io.ktor.response.*
import io.ktor.util.*
import io.ktor.util.cio.*
import io.ktor.utils.io.*
import java.io.*
import java.util.*
/**
......@@ -57,42 +58,14 @@ public class Pebble(private val engine: PebbleEngine) {
}
}
private fun process(content: PebbleContent): PebbleOutgoingContent {
return PebbleOutgoingContent(
engine.getTemplate(content.template),
content.model,
content.locale,
content.etag,
content.contentType
)
}
private fun process(content: PebbleContent): OutgoingContent = with(content) {
val writer = StringWriter()
engine.getTemplate(content.template).evaluate(writer, model, locale)
/**
* Content which is responded when Pebble templates are rendered.
*
* @param template the compiled [com.mitchellbosecke.pebble.template.PebbleTemplate] template
* @param model the model provided into the template
* @param locale which is used to resolve templates (optional)
* @param etag value for `E-Tag` header (optional)
* @param contentType response's content type which is set to `text/html;charset=utf-8` by default
*/
private class PebbleOutgoingContent(
val template: PebbleTemplate,
val model: Map<String, Any>,
val locale: Locale?,
etag: String?,
override val contentType: ContentType
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
channel.bufferedWriter(contentType.charset() ?: Charsets.UTF_8).use {
template.evaluate(it, model, locale)
}
}
init {
if (etag != null) {
versions += EntityTagVersion(etag)
}
val result = TextContent(text = writer.toString(), contentType)
if (etag != null) {
result.versions += EntityTagVersion(etag)
}
return result
}
}
......@@ -100,7 +100,9 @@ class PebbleTest {
fun `Render template compressed with GZIP`() {
withTestApplication {
application.setupPebble()
application.install(Compression)
application.install(Compression) {
gzip { minimumSize(10) }
}
application.install(ConditionalHeaders)
application.routing {
......
......@@ -4,20 +4,14 @@
package io.ktor.thymeleaf
import io.ktor.application.ApplicationCallPipeline
import io.ktor.application.ApplicationFeature
import io.ktor.http.ContentType
import io.ktor.http.charset
import io.ktor.http.content.EntityTagVersion
import io.ktor.http.content.OutgoingContent
import io.ktor.http.content.versions
import io.ktor.http.withCharset
import io.ktor.response.ApplicationSendPipeline
import io.ktor.util.AttributeKey
import io.ktor.util.cio.bufferedWriter
import io.ktor.utils.io.ByteWriteChannel
import org.thymeleaf.TemplateEngine
import org.thymeleaf.context.Context
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.response.*
import io.ktor.util.*
import org.thymeleaf.*
import org.thymeleaf.context.*
import java.io.*
/**
* Represents a content handled by [Thymeleaf] feature.
......@@ -57,34 +51,15 @@ public class Thymeleaf(private val engine: TemplateEngine) {
}
}
private fun process(content: ThymeleafContent): ThymeleafOutgoingContent {
return ThymeleafOutgoingContent(
engine,
content.template,
content.model,
content.etag,
content.contentType
)
}
private fun process(content: ThymeleafContent): OutgoingContent = with(content) {
val writer = StringWriter()
val context = Context().apply { setVariables(model) }
engine.process(template, context, writer)
private class ThymeleafOutgoingContent(
val engine: TemplateEngine,
val template: String,
val model: Map<String, Any>,
etag: String?,
override val contentType: ContentType
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
channel.bufferedWriter(contentType.charset() ?: Charsets.UTF_8).use {
val context = Context().apply { setVariables(model) }
engine.process(template, context, it)
}
}
init {
if (etag != null) {
versions += EntityTagVersion(etag)
}
val result = TextContent(text = writer.toString(), contentType)
if (etag != null) {
result.versions += EntityTagVersion(etag)
}
return result
}
}
......@@ -44,7 +44,9 @@ class ThymeleafTest {
fun testCompression() {
withTestApplication {
application.setUpThymeleafStringTemplate()
application.install(Compression)
application.install(Compression) {
gzip { minimumSize(10) }
}
application.install(ConditionalHeaders)
application.routing {
......
......@@ -14,6 +14,7 @@ import io.ktor.utils.io.*
import org.apache.velocity.*
import org.apache.velocity.app.*
import org.apache.velocity.context.*
import java.io.*
/**
* Represents a response content that could be used to respond with `call.respond(VelocityContent(...))`
......@@ -30,23 +31,20 @@ public class VelocityContent(
public val contentType: ContentType = ContentType.Text.Html.withCharset(Charsets.UTF_8)
)
internal class VelocityOutgoingContent(
val template: Template,
val model: Context,
internal fun velocityOutgoingContent(
template: Template,
model: Context,
etag: String?,
override val contentType: ContentType
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
channel.bufferedWriter(contentType.charset() ?: Charsets.UTF_8).use {
template.merge(model, it)
}
}
contentType: ContentType
): OutgoingContent {
val writer = StringWriter()
template.merge(model, writer)
init {
if (etag != null) {
versions += EntityTagVersion(etag)
}
val result = TextContent(text = writer.toString(), contentType)
if (etag != null) {
result.versions += EntityTagVersion(etag)
}
return result
}
/**
......@@ -76,8 +74,8 @@ public class Velocity(private val engine: VelocityEngine) {
}
}
private fun process(content: VelocityContent): VelocityOutgoingContent {
return VelocityOutgoingContent(
private fun process(content: VelocityContent): OutgoingContent {
return velocityOutgoingContent(
engine.getTemplate(content.template),
VelocityContext(content.model),
content.etag,
......
......@@ -5,6 +5,7 @@
package io.ktor.velocity
import io.ktor.application.*
import io.ktor.http.content.*
import io.ktor.response.*
import io.ktor.util.*
import org.apache.velocity.app.*
......@@ -54,8 +55,8 @@ public class VelocityTools private constructor(private val toolManager: ToolMana
}
}
internal fun process(content: VelocityContent): VelocityOutgoingContent {
return VelocityOutgoingContent(
internal fun process(content: VelocityContent): OutgoingContent {
return velocityOutgoingContent(
toolManager.velocityEngine.getTemplate(content.template),
toolManager.createContext().also { it.putAll(content.model) },
content.etag,
......
......@@ -46,7 +46,9 @@ class VelocityTest {
fun testCompression() {
withTestApplication {
application.setUpTestTemplates()
application.install(Compression)
application.install(Compression) {
gzip { minimumSize(10) }
}
application.install(ConditionalHeaders)
application.routing {
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать