diff --git a/server/src/main/scala/org/scalaexercises/evaluator/services.scala b/server/src/main/scala/org/scalaexercises/evaluator/services.scala index 44c9001c..e1679773 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/services.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/services.scala @@ -5,17 +5,17 @@ package org.scalaexercises.evaluator -import org.http4s._, org.http4s.dsl._, org.http4s.server._ +import org.http4s._ +import org.http4s.dsl._ +import org.http4s.server._ import org.http4s.server.blaze._ import org.log4s.getLogger - import monix.execution.Scheduler -import scala.concurrent.duration._ import scala.language.postfixOps - import scalaz.concurrent.Task import scalaz._ +import scala.concurrent.duration._ object services { @@ -29,47 +29,62 @@ object services { val evaluator = new Evaluator(20 seconds) + val corsHeaders = Seq( + Header("Vary", "Origin,Access-Control-Request-Methods"), + Header("Access-Control-Allow-Methods", "POST"), + Header("Access-Control-Allow-Origin", "*"), + Header( + "Access-Control-Allow-Headers", + "x-scala-eval-api-token, Content-Type"), + Header("Access-Control-Max-Age", 1.day.toSeconds.toString())) + def evalService = auth(HttpService { case req @ POST -> Root / "eval" => import io.circe.syntax._ - req.decode[EvalRequest] { - evalRequest => - evaluator.eval[Any]( - code = evalRequest.code, - remotes = evalRequest.resolvers, - dependencies = evalRequest.dependencies - ) flatMap { - result => - val response = result match { - case EvalSuccess(cis, res, out) => - EvalResponse( - `ok`, - Option(res.toString), - Option(res.asInstanceOf[AnyRef].getClass.getName), - cis) - case Timeout(_) => - EvalResponse(`Timeout Exceded`, None, None, Map.empty) - case UnresolvedDependency(msg) => - EvalResponse( - `Unresolved Dependency` + " : " + msg, - None, - None, - Map.empty) - case EvalRuntimeError(cis, runtimeError) => - EvalResponse( - `Runtime Error`, - runtimeError map (_.error.getMessage), - runtimeError map (_.error.getClass.getName), - cis) - case CompilationError(cis) => - EvalResponse(`Compilation Error`, None, None, cis) - case GeneralError(err) => - EvalResponse(`Unforeseen Exception`, None, None, Map.empty) - } - Ok(response.asJson) - } - } + req + .decode[EvalRequest] { + evalRequest => + evaluator.eval[Any]( + code = evalRequest.code, + remotes = evalRequest.resolvers, + dependencies = evalRequest.dependencies + ) flatMap { + (result: EvalResult[_]) => + val response = result match { + case EvalSuccess(cis, res, out) => + EvalResponse( + `ok`, + Option(res.toString), + Option(res.asInstanceOf[AnyRef].getClass.getName), + cis) + case Timeout(_) => + EvalResponse(`Timeout Exceded`, None, None, Map.empty) + case UnresolvedDependency(msg) => + EvalResponse( + `Unresolved Dependency` + " : " + msg, + None, + None, + Map.empty) + case EvalRuntimeError(cis, runtimeError) => + EvalResponse( + `Runtime Error`, + runtimeError map (_.error.getMessage), + runtimeError map (_.error.getClass.getName), + cis) + case CompilationError(cis) => + EvalResponse(`Compilation Error`, None, None, cis) + case GeneralError(err) => + EvalResponse( + `Unforeseen Exception`, + None, + None, + Map.empty) + } + Ok(response.asJson) + } + } + .map((r: Response) => r.putHeaders(corsHeaders: _*)) }) def loaderIOService = HttpService { @@ -81,6 +96,12 @@ object services { Ok("loaderio-1318d1b3e06b7bc96dd5de5716f57496") } + // CORS middleware in http4s can't be combined with our `auth` middleware. We need to handle CORS calls ourselves. + def optionsService = HttpService { + case OPTIONS -> Root / "eval" => + Ok().putHeaders(corsHeaders: _*) + } + } object EvaluatorServer extends App { @@ -96,9 +117,14 @@ object EvaluatorServer extends App { logger.info(s"Initializing Evaluator at $ip:$port") + // The order in which services are mounted is really important. They're executed from bottom to top, and they won't be + // checked for repeated methods or routes. That's why we set the `optionsService` before the `evalService` one, as if + // not, execution of a OPTIONS call to /eval would lead to `evalService`, even if that service doesn't recognize the + // OPTIONS verb. BlazeBuilder .bindHttp(port, ip) .mountService(evalService) + .mountService(optionsService) .mountService(loaderIOService) .start .run