Permalink
Browse files

Better Delta support to automatically handle HTML in applications whe…

…n deltas are assigned

Added support to ProxyCache to create a Delta for processing script, img, and link tags to update to use caching
Fixes to Server.handle to avoid potential for exceptions not getting handled in certain circumstances
  • Loading branch information...
darkfrog26 committed Jun 14, 2018
1 parent 6b084fc commit b9d49f237d25c830350cc9ea4ce8fc2e537a368a
View
@@ -4,4 +4,5 @@ node_modules/
npm-debug.log
*.js
*.js.map
package-lock.json
package-lock.json
temp/
@@ -180,8 +180,8 @@ trait ServerApplication extends YouIApplication with Server {
* @return HttpHandler that has already been added to the server
*/
def deltas(function: HttpConnection => List[Delta]): HttpHandler = builder.handle { connection =>
val d = function(connection)
addDeltas(connection, d)
val d: List[Delta] = function(connection)
connection.deltas += d
}
def page(template: Content = ServerApplication.CanvasTemplate,
@@ -191,10 +191,15 @@ trait ServerApplication extends YouIApplication with Server {
}
}
def addDeltas(connection: HttpConnection, deltas: List[Delta]): Unit = {
if (deltas.nonEmpty) {
val current = connection.store.getOrElse[List[Delta]](ServerApplication.DeltaKey, Nil)
connection.store(ServerApplication.DeltaKey) = current ::: deltas
override protected def handleInternal(connection: HttpConnection): Unit = {
super.handleInternal(connection)
connection.response.content match {
case Some(content) if content.contentType == ContentType.`text/html` && connection.deltas.nonEmpty => {
connection.update(_.removeContent())
serveHTML(connection, content, Nil, includeApplication = false)
}
case _ => // Ignore
}
}
@@ -207,7 +212,7 @@ trait ServerApplication extends YouIApplication with Server {
val responseFields = responseMap(httpConnection).toList.map {
case (name, value) => s"""<input type="hidden" id="$name" value="$value"/>"""
}
val deltasList = httpConnection.store.getOrElse[List[Delta]](ServerApplication.DeltaKey, Nil) ::: deltas
val deltasList = httpConnection.deltas() ::: deltas
val applicationDeltas = if (includeApplication) {
val jsDeps = if (applicationJSDepsContent.nonEmpty) {
s"""<script src="$applicationJSDepsPath"></script>"""
@@ -233,6 +238,7 @@ trait ServerApplication extends YouIApplication with Server {
val d = applicationDeltas ::: deltasList
val selector = httpConnection.request.url.param("selector").map(Selector.parse)
val html = stream.stream(d, selector)
httpConnection.deltas.clear()
SenderHandler.handle(httpConnection, Content.string(html, ContentType.`text/html`), caching = CachingManager.NotCached)
}
@@ -343,6 +349,4 @@ object ServerApplication {
|</body>
|</html>
""".stripMargin.trim, ContentType.`text/html`)
val DeltaKey: String = "deltas"
}
@@ -5,7 +5,6 @@ import io.youi.http._
import io.youi.net._
import io.youi.server.handler.{CachingManager, ProxyCache}
import io.youi.server.dsl._
import io.youi.stream._
object ServerExampleApplication extends ExampleApplication with ServerApplication {
val generalPages: Page = page(GeneralPages)
@@ -24,15 +23,7 @@ object ServerExampleApplication extends ExampleApplication with ServerApplicatio
combined.any(
path.matches("/examples/.*[.]html"),
path.exact("/ui-examples.html")
) / Application / Delta.Process(
selector = ByTag("script"),
replace = true,
onlyOpenTag = true,
processor = (tag, content) => {
scribe.info(s"Process: $content")
content
}
) / ServerApplication.AppTemplate,
) / Application / ProxyCache.delta("/cache") / ServerApplication.AppTemplate,
path"/cookies.html" / CookiesExample,
path"/session.html" / SessionExample,
path"/cache" / ProxyCache(),
@@ -1,6 +1,7 @@
package io.youi.http
import io.youi.server.Server
import io.youi.stream.Delta
import io.youi.{MapStore, Store}
class HttpConnection(val server: Server, val request: HttpRequest) {
@@ -28,6 +29,19 @@ class HttpConnection(val server: Server, val request: HttpRequest) {
}
}
object deltas {
private val key: String = "deltas"
def apply(): List[Delta] = store.getOrElse[List[Delta]](key, Nil)
def clear(): Unit = store.remove(key)
def +=(deltas: List[Delta]): Unit = store(key) = apply() ::: deltas
def +=(delta: Delta): Unit = this += List(delta)
def isEmpty: Boolean = apply().isEmpty
def nonEmpty: Boolean = apply().nonEmpty
}
def isFinished: Boolean = finished
def finish(): Unit = finished = true
}
@@ -82,22 +82,24 @@ trait Server extends HttpHandler with ErrorSupport {
SessionStore.dispose()
}
override def handle(connection: HttpConnection): Unit = {
try {
handleRecursive(connection, handlers())
// NotFound handling
if (connection.response.content.isEmpty && connection.response.status == HttpStatus.OK) {
connection.update { response =>
response.copy(status = HttpStatus.NotFound)
}
errorHandler.get.handle(connection, None)
}
} catch {
case t: Throwable => {
error(t)
errorHandler.get.handle(connection, Some(t))
override final def handle(connection: HttpConnection): Unit = try {
handleInternal(connection)
} catch {
case t: Throwable => {
error(t)
errorHandler.get.handle(connection, Some(t))
}
}
protected def handleInternal(connection: HttpConnection): Unit = {
handleRecursive(connection, handlers())
// NotFound handling
if (connection.response.content.isEmpty && connection.response.status == HttpStatus.OK) {
connection.update { response =>
response.copy(status = HttpStatus.NotFound)
}
errorHandler.get.handle(connection, None)
}
}
@@ -35,41 +35,14 @@ package object dsl {
implicit class CachingManagerFilter(val caching: CachingManager) extends LastConnectionFilter(new ActionFilter(caching.handle))
implicit class DeltasFilter(val deltas: List[Delta]) extends ActionFilter(processDeltas(_, deltas))
implicit class DeltasFilter(val deltas: List[Delta]) extends ActionFilter(_.deltas += deltas)
implicit class DeltaFilter(delta: Delta) extends ActionFilter(processDeltas(_, List(delta)))
implicit class DeltaFilter(delta: Delta) extends ActionFilter(_.deltas += delta)
implicit class StringFilter(val s: String) extends ConnectionFilter {
override def filter(connection: HttpConnection): Option[HttpConnection] = PathPart.take(connection, s)
}
private[server] def processDeltas(connection: HttpConnection, deltas: List[Delta] = Nil): Unit = {
scribe.info(s"processing deltas: $deltas")
connection.response.content match {
case Some(content) => {
val stream: StreamableHTML = content match {
case c: FileContent => HTMLParser.cache(c.file)
case c: URLContent => HTMLParser.cache(c.url)
case c: StringContent => HTMLParser.cache(c.value)
}
val deltasList = connection.store.getOrElse[List[Delta]](DeltaKey, Nil) ::: deltas
if (deltasList.nonEmpty) {
val selector = connection.request.url.param("selector").map(Selector.parse)
val streamed = stream.stream(deltasList, selector)
connection.update { response =>
response.withContent(Content.string(streamed, content.contentType))
}
}
}
case None => { // No content
if (deltas.nonEmpty) {
scribe.warn(s"Not content set for processing of deltas: $deltas")
}
}
}
}
implicit class URLMatcherFilter(val matcher: URLMatcher) extends ConditionalFilter(c => matcher.matches(c.request.url))
case class ClassLoaderPath(directory: String = "", pathTransform: String => String = (s: String) => s) extends ConnectionFilter {
@@ -1,13 +1,9 @@
package io.youi.server.handler
import io.youi.http.{Content, HttpConnection, HttpStatus}
import io.youi.server.dsl
case class ContentHandler(content: Content, status: HttpStatus) extends HttpHandler {
override def handle(connection: HttpConnection): Unit = {
connection.update { response =>
response.copy(status = status, content = Some(content))
}
dsl.processDeltas(connection)
override def handle(connection: HttpConnection): Unit = connection.update { response =>
response.copy(status = status, content = Some(content))
}
}
@@ -4,6 +4,7 @@ import java.nio.file.{Path, Paths}
import io.youi.http.{Content, HttpConnection, HttpStatus}
import io.youi.net.ContentType
import io.youi.stream.{ByMultiple, ByTag, Delta}
import org.powerscala.io._
case class ProxyCache(directory: Path = Paths.get(System.getProperty("java.io.tmpdir"))) extends HttpHandler {
@@ -29,3 +30,25 @@ case class ProxyCache(directory: Path = Paths.get(System.getProperty("java.io.tm
case None => connection.update(_.withStatus(HttpStatus.BadRequest).withContent(Content.string("Must include `url` as GET parameter", ContentType.`text/plain`)))
}
}
object ProxyCache {
def delta(cachePath: String): Delta = Delta.Process(
selector = ByMultiple(ByTag("script"), ByTag("img"), ByTag("link")),
replace = true,
onlyOpenTag = true,
processor = (tag, content) => {
def fixContent(attributeName: String): String = {
val value = tag.attributes.getOrElse(attributeName, "")
if (value.toLowerCase.startsWith("http")) {
content.replaceAllLiterally(value, s"/cache?url=${URLEncoder.encode(value, "UTF-8")}")
} else {
content
}
}
tag.tagName.toLowerCase match {
case "script" | "img" => fixContent("src")
case "link" => fixContent("href")
}
}
)
}

0 comments on commit b9d49f2

Please sign in to comment.