Skip to content

Commit

Permalink
Began migration to a much more powerful asynchronous HTTP functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
darkfrog26 committed Jan 27, 2019
1 parent cf2434d commit 1be645d
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 50 deletions.
24 changes: 10 additions & 14 deletions server/src/main/scala/io/youi/http/HttpConnection.scala
Expand Up @@ -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 {
Expand All @@ -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)
}
20 changes: 12 additions & 8 deletions server/src/main/scala/io/youi/server/DefaultErrorHandler.scala
Expand Up @@ -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()

Expand All @@ -18,14 +20,16 @@ object DefaultErrorHandler extends ErrorHandler {
</body>
</html>""".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))
}
}
4 changes: 3 additions & 1 deletion server/src/main/scala/io/youi/server/ErrorHandler.scala
Expand Up @@ -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]
}
37 changes: 18 additions & 19 deletions server/src/main/scala/io/youi/server/Server.scala
Expand Up @@ -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)
}
}
}
}
Expand Down
Expand Up @@ -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))
}
}
}
10 changes: 6 additions & 4 deletions server/src/main/scala/io/youi/server/handler/HttpHandler.scala
Expand Up @@ -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()
}
Expand Down
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 1be645d

Please sign in to comment.