From 1be645dcb84fd944b4265fcb58ac3fc04aa338b2 Mon Sep 17 00:00:00 2001 From: Matt Hicks Date: Sat, 26 Jan 2019 21:00:44 -0600 Subject: [PATCH] Began migration to a much more powerful asynchronous HTTP functionality --- .../scala/io/youi/http/HttpConnection.scala | 24 +++++------- .../io/youi/server/DefaultErrorHandler.scala | 20 ++++++---- .../scala/io/youi/server/ErrorHandler.scala | 4 +- .../main/scala/io/youi/server/Server.scala | 37 +++++++++---------- .../youi/server/handler/ContentHandler.scala | 8 +++- .../io/youi/server/handler/HttpHandler.scala | 10 +++-- .../server/handler/HttpHandlerBuilder.scala | 6 ++- 7 files changed, 59 insertions(+), 50 deletions(-) diff --git a/server/src/main/scala/io/youi/http/HttpConnection.scala b/server/src/main/scala/io/youi/http/HttpConnection.scala index f8476d2a6..5de2437bc 100644 --- a/server/src/main/scala/io/youi/http/HttpConnection.scala +++ b/server/src/main/scala/io/youi/http/HttpConnection.scala @@ -4,24 +4,21 @@ import io.youi.server.Server import io.youi.stream.Delta import io.youi.{MapStore, Store} -class HttpConnection(val server: Server, val request: HttpRequest) { - private var responseVar: HttpResponse = HttpResponse() - private var finished = false - - val store: Store = new MapStore() - - def response: HttpResponse = responseVar - - def update(f: HttpResponse => HttpResponse): Unit = synchronized { - responseVar = f(responseVar) +case class HttpConnection(server: Server, + request: HttpRequest, + response: HttpResponse = HttpResponse(), + finished: Boolean = false, + store: Store = new MapStore()) { + def modify(f: HttpResponse => HttpResponse): HttpConnection = { + copy(response = f(response)) } def isWebSocketUpgradeRequest: Boolean = Headers.`Connection`.all(request.headers).contains("Upgrade") def webSocketSupport: Option[Connection] = store.get[Connection](Connection.key) - def webSocketSupport_=(listener: Connection): Unit = { + def withWebSocket(listener: Connection): HttpConnection = { if (isWebSocketUpgradeRequest) { store.update(Connection.key, listener) - update { response => + modify { response => response.copy(status = HttpStatus.SwitchingProtocols) } } else { @@ -42,6 +39,5 @@ class HttpConnection(val server: Server, val request: HttpRequest) { def nonEmpty: Boolean = apply().nonEmpty } - def isFinished: Boolean = finished - def finish(): Unit = finished = true + def finish(): HttpConnection = copy(finished = true) } \ No newline at end of file diff --git a/server/src/main/scala/io/youi/server/DefaultErrorHandler.scala b/server/src/main/scala/io/youi/server/DefaultErrorHandler.scala index f9cf2e9e0..02352cca9 100644 --- a/server/src/main/scala/io/youi/server/DefaultErrorHandler.scala +++ b/server/src/main/scala/io/youi/server/DefaultErrorHandler.scala @@ -6,6 +6,8 @@ import io.youi.net.ContentType import io.youi.server.dsl._ import perfolation._ +import scala.concurrent.Future + object DefaultErrorHandler extends ErrorHandler { lazy val lastModified: Long = System.currentTimeMillis() @@ -18,14 +20,16 @@ object DefaultErrorHandler extends ErrorHandler { """.withContentType(ContentType.`text/html`).withLastModified(lastModified) - override def handle(connection: HttpConnection, t: Option[Throwable]): Unit = connection.update { response => - val status = if (response.status.isError) { - response.status - } else { - HttpStatus.InternalServerError + override def handle(connection: HttpConnection, t: Option[Throwable]): Future[HttpConnection] = Future.successful { + connection.modify { response => + val status = if (response.status.isError) { + response.status + } else { + HttpStatus.InternalServerError + } + response + .withContent(html(status)) + .withHeader(CacheControl(CacheControl.NoCache)) } - response - .withContent(html(status)) - .withHeader(CacheControl(CacheControl.NoCache)) } } \ No newline at end of file diff --git a/server/src/main/scala/io/youi/server/ErrorHandler.scala b/server/src/main/scala/io/youi/server/ErrorHandler.scala index 3daa333f0..0fc144e1c 100644 --- a/server/src/main/scala/io/youi/server/ErrorHandler.scala +++ b/server/src/main/scala/io/youi/server/ErrorHandler.scala @@ -2,6 +2,8 @@ package io.youi.server import io.youi.http.HttpConnection +import scala.concurrent.Future + trait ErrorHandler { - def handle(connection: HttpConnection, t: Option[Throwable]): Unit + def handle(connection: HttpConnection, t: Option[Throwable]): Future[HttpConnection] } diff --git a/server/src/main/scala/io/youi/server/Server.scala b/server/src/main/scala/io/youi/server/Server.scala index 04c94127d..a5c8c94ee 100644 --- a/server/src/main/scala/io/youi/server/Server.scala +++ b/server/src/main/scala/io/youi/server/Server.scala @@ -89,36 +89,35 @@ trait Server extends HttpHandler with ErrorSupport { SessionStore.dispose() } - override final def handle(connection: HttpConnection): Unit = try { - handleInternal(connection) - } catch { - case t: Throwable => { + override final def handle(connection: HttpConnection): Future[HttpConnection] = handleInternal(connection).recoverWith { + case t => { 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) + protected def handleInternal(connection: HttpConnection): Future[HttpConnection] = { + handleRecursive(connection, handlers()).flatMap { updated => + // NotFound handling + if (updated.response.content.isEmpty && updated.response.status == HttpStatus.OK) { + updated.modify { response => + response.copy(status = HttpStatus.NotFound) + } + errorHandler.get.handle(updated, None) + } else { + Future.successful(updated) } - errorHandler.get.handle(connection, None) } } - @tailrec - private def handleRecursive(connection: HttpConnection, handlers: List[HttpHandler]): Unit = { - if (connection.isFinished || handlers.isEmpty) { - // Finished + private def handleRecursive(connection: HttpConnection, handlers: List[HttpHandler]): Future[HttpConnection] = { + if (connection.finished || handlers.isEmpty) { + Future.successful(connection) // Finished } else { val handler = handlers.head - handler.handle(connection) - - handleRecursive(connection, handlers.tail) + handler.handle(connection).flatMap { updated => + handleRecursive(updated, handlers.tail) + } } } } diff --git a/server/src/main/scala/io/youi/server/handler/ContentHandler.scala b/server/src/main/scala/io/youi/server/handler/ContentHandler.scala index f942a2a41..5af031ebe 100644 --- a/server/src/main/scala/io/youi/server/handler/ContentHandler.scala +++ b/server/src/main/scala/io/youi/server/handler/ContentHandler.scala @@ -3,8 +3,12 @@ package io.youi.server.handler import io.youi.http.content.Content import io.youi.http.{HttpConnection, HttpStatus} +import scala.concurrent.Future + 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)) + override def handle(connection: HttpConnection): Future[HttpConnection] = Future.successful { + connection.modify { response => + response.copy(status = status, content = Some(content)) + } } } \ No newline at end of file diff --git a/server/src/main/scala/io/youi/server/handler/HttpHandler.scala b/server/src/main/scala/io/youi/server/handler/HttpHandler.scala index 1043d656f..732070be0 100644 --- a/server/src/main/scala/io/youi/server/handler/HttpHandler.scala +++ b/server/src/main/scala/io/youi/server/handler/HttpHandler.scala @@ -4,22 +4,24 @@ import io.youi.Priority import io.youi.http.content.Content import io.youi.http.{HttpConnection, HttpStatus, StringHeaderKey} +import scala.concurrent.Future + trait HttpHandler extends Ordered[HttpHandler] { def priority: Priority = Priority.Normal - def handle(connection: HttpConnection): Unit + def handle(connection: HttpConnection): Future[HttpConnection] override def compare(that: HttpHandler): Int = priority.compare(that.priority) } object HttpHandler { - def redirect(connection: HttpConnection, location: String): Unit = { + def redirect(connection: HttpConnection, location: String): HttpConnection = { val isStreaming = connection.request.headers.first(new StringHeaderKey("streaming")).contains("true") if (isStreaming) { val status = HttpStatus.NetworkAuthenticationRequired(s"Redirect to $location") - connection.update(_.withStatus(status).withContent(Content.empty)) + connection.modify(_.withStatus(status).withContent(Content.empty)) } else { - connection.update(_.withRedirect(location)) + connection.modify(_.withRedirect(location)) } connection.finish() } diff --git a/server/src/main/scala/io/youi/server/handler/HttpHandlerBuilder.scala b/server/src/main/scala/io/youi/server/handler/HttpHandlerBuilder.scala index acedfef3e..0e586428b 100644 --- a/server/src/main/scala/io/youi/server/handler/HttpHandlerBuilder.scala +++ b/server/src/main/scala/io/youi/server/handler/HttpHandlerBuilder.scala @@ -13,6 +13,8 @@ import io.youi.server.Server import io.youi.server.validation.{ValidationResult, Validator} import io.youi.stream.{Delta, HTMLParser, Selector} +import scala.concurrent.Future + case class HttpHandlerBuilder(server: Server, urlMatcher: Option[URLMatcher] = None, requestMatchers: Set[HttpRequest => Boolean] = Set.empty, @@ -90,8 +92,8 @@ case class HttpHandlerBuilder(server: Server, } } - def handle(f: HttpConnection => Unit): HttpHandler = wrap(new HttpHandler { - override def handle(connection: HttpConnection): Unit = f(connection) + def handle(f: HttpConnection => Future[HttpConnection]): HttpHandler = wrap(new HttpHandler { + override def handle(connection: HttpConnection): Future[HttpConnection] = f(connection) }) def validation(validator: HttpConnection => ValidationResult): HttpHandler = validation(new Validator {