Skip to content

Commit

Permalink
Merge pull request #62 from scala-exercises/enrique-update-server
Browse files Browse the repository at this point in the history
Evaluator Server Update
  • Loading branch information
kiroco12 committed Sep 19, 2019
2 parents 23d62e5 + 388d9d6 commit a777e75
Show file tree
Hide file tree
Showing 16 changed files with 193 additions and 192 deletions.
24 changes: 12 additions & 12 deletions project/ProjectPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object ProjectPlugin extends AutoPlugin {
lazy val roshttp = "2.2.4"
lazy val slf4jSimple = "1.7.28"
lazy val jwtCore = "4.0.0"
lazy val coursier = "2.0.0-RC3-2"
lazy val coursier = "2.0.0-RC3-4"
}

lazy val dockerSettings = Seq(
Expand Down Expand Up @@ -82,9 +82,11 @@ object ProjectPlugin extends AutoPlugin {
%%("http4s-circe", V.http4s),
%("config"),
%%("jwt-core", V.jwtCore),
%%("coursier", V.coursier),
%%("coursier-cache", V.coursier),
"io.get-coursier" %% "coursier-cats-interop" % V.coursier,
%%("scalatest", V.scalatest) % "test"
),
addSbtPlugin("io.get-coursier" % "sbt-coursier" % V.coursier)
)
)

lazy val buildInfoSettings = Seq(
Expand Down Expand Up @@ -136,21 +138,19 @@ object ProjectPlugin extends AutoPlugin {
organizationEmail = "hello@47deg.com"
),
orgLicenseSetting := ApacheLicense,
scalaVersion := "2.12.10",
scalaVersion := "2.11.11",
scalaOrganization := "org.scala-lang",
javacOptions ++= Seq("-encoding", "UTF-8", "-Xlint:-options"),
fork in Test := false,
parallelExecution in Test := false,
cancelable in Global := true,
headerLicense := Some(
HeaderLicense.Custom(
s"""|/*
| * scala-exercises - ${name.value}
| * Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
| */
|
|""".stripMargin
)),
headerMappings := headerMappings.value + (HeaderFileType.scala -> HeaderCommentStyle.CStyleBlockComment)
s"""|
| scala-exercises - ${name.value}
| Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
|
|""".stripMargin
))
) ++ shellPromptSettings
}
55 changes: 31 additions & 24 deletions server/src/main/scala/org/scalaexercises/evaluator/auth.scala
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
/*
* scala-exercises - evaluator-server
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*
* scala-exercises - evaluator-server
* Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
*
*/

package org.scalaexercises.evaluator

import org.http4s._, org.http4s.dsl._, org.http4s.server._
import cats.effect.Sync
import com.typesafe.config._
import org.http4s._
import org.http4s.syntax.kleisli.http4sKleisliResponseSyntax
import org.http4s.util._
import scala.util.{Failure, Success, Try}
import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim, JwtHeader, JwtOptions}

import org.log4s.getLogger
import pdi.jwt.{Jwt, JwtAlgorithm}

import scalaz.concurrent.Task
import scala.util.{Failure, Success}

object auth {

Expand All @@ -37,15 +39,14 @@ object auth {

type HeaderT = `X-Scala-Eval-Api-Token`

def name: CaseInsensitiveString = "x-scala-eval-api-token".ci
def name: CaseInsensitiveString = CaseInsensitiveString("x-scala-eval-api-token")

override def parse(s: String): ParseResult[`X-Scala-Eval-Api-Token`] =
ParseResult.success(`X-Scala-Eval-Api-Token`(s))

def matchHeader(header: Header): Option[HeaderT] = {
def matchHeader(header: Header): Option[HeaderT] =
if (header.name == name) Some(`X-Scala-Eval-Api-Token`(header.value))
else None
}

}

Expand All @@ -55,20 +56,26 @@ object auth {
writer.append(token)
}

def apply(service: HttpService): HttpService = Service.lift { req =>
req.headers.get(`X-Scala-Eval-Api-Token`) match {
case Some(header) =>
Jwt.decodeRaw(header.value, secretKey, Seq(JwtAlgorithm.HS256)) match {
case Success(tokenIdentity) =>
logger.info(s"Auth success with identity : $tokenIdentity")
service(req)
case Failure(ex) =>
logger.warn(s"Auth failed : $ex")
Task.now(Response(Status.Unauthorized))
def apply[F[_]: Sync](service: HttpApp[F]): HttpApp[F] =
HttpRoutes
.of[F] {
case req if req.headers.nonEmpty => {
req.headers.get(`X-Scala-Eval-Api-Token`) match {
case Some(header) =>
Jwt.decodeRaw(header.token, secretKey, Seq(JwtAlgorithm.HS256)) match {
case Success(tokenIdentity) => {
logger.info(s"Auth success with identity : $tokenIdentity")
service(req)
}
case Failure(ex) => {
logger.warn(s"Auth failed : $ex")
Sync[F].pure(Response(Status.Unauthorized))
}
}
}
}
case None => Task.now(Response(Status.Unauthorized))
}

}
case _ => Sync[F].pure(Response(Status.Unauthorized))
}
.orNotFound

}
37 changes: 10 additions & 27 deletions server/src/main/scala/org/scalaexercises/evaluator/codecs.scala
Original file line number Diff line number Diff line change
@@ -1,40 +1,23 @@
/*
* scala-exercises - evaluator-server
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*
* scala-exercises - evaluator-server
* Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
*
*/

package org.scalaexercises.evaluator

import org.http4s._, org.http4s.dsl._
import io.circe.{Decoder, Encoder, Json, Printer}
import org.http4s.headers.`Content-Type`
import io.circe.jawn.CirceSupportParser.facade
import cats.effect.Sync
import io.circe.{Decoder, Encoder}
import org.http4s._
import org.http4s.circe._

/** Provides Json serialization codecs for the http4s services */
trait Http4sCodecInstances {

implicit val jsonDecoder: EntityDecoder[Json] = jawn.jawnDecoder(facade)
implicit def entityDecoderOf[F[_]: Sync, A: Decoder]: EntityDecoder[F, A] = jsonOf[F, A]

implicit def jsonDecoderOf[A](implicit decoder: Decoder[A]): EntityDecoder[A] =
jsonDecoder.flatMapR { json =>
decoder
.decodeJson(json)
.fold(
failure =>
DecodeResult.failure(
InvalidMessageBodyFailure(s"Could not decode JSON: $json", Some(failure))),
DecodeResult.success(_)
)
}

implicit val jsonEntityEncoder: EntityEncoder[Json] = EntityEncoder[String]
.contramap[Json] { json =>
Printer.noSpaces.pretty(json)
}
.withContentType(`Content-Type`(MediaType.`application/json`))

implicit def jsonEncoderOf[A](implicit encoder: Encoder[A]): EntityEncoder[A] =
jsonEntityEncoder.contramap[A](encoder.apply)
implicit def entityEncoderOf[F[_]: Sync, A: Encoder]: EntityEncoder[F, A] = jsonEncoderOf[F, A]

}

Expand Down
95 changes: 54 additions & 41 deletions server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/*
* scala-exercises - evaluator-server
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*
* scala-exercises - evaluator-server
* Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
*
*/

package org.scalaexercises.evaluator
Expand All @@ -9,11 +11,13 @@ import java.io.{ByteArrayOutputStream, File}
import java.math.BigInteger
import java.net.URLClassLoader
import java.security.MessageDigest
import java.util.concurrent.TimeoutException
import java.util.jar.JarFile

import cats.effect.{Concurrent, ConcurrentEffect, ContextShift, Timer}
import cats.implicits._
import coursier._
import monix.execution.Scheduler
import coursier.cache.{ArtifactError, FileCache}
import coursier.util.Sync
import org.scalaexercises.evaluator.Eval.CompilerException

import scala.concurrent.duration._
Expand All @@ -24,45 +28,47 @@ import scala.tools.nsc.reporters._
import scala.tools.nsc.{Global, Settings}
import scala.util.Try
import scala.util.control.NonFatal
import scalaz.Scalaz._
import scalaz._
import scalaz.concurrent.Task

class Evaluator(timeout: FiniteDuration = 20.seconds)(
implicit S: Scheduler
) {
class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(
implicit CS: ContextShift[F],
F: ConcurrentEffect[F],
T: Timer[F]) {
type Remote = String

private[this] def convert(errors: (Position, String, String)): (String, List[CompilationInfo]) = {
val (pos, msg, severity) = errors
(severity, CompilationInfo(msg, Some(RangePosition(pos.start, pos.point, pos.end))) :: Nil)
}

def remoteToRepository(remote: Remote): Repository =
MavenRepository(remote)
def remoteToRepository(remote: Remote): Repository = MavenRepository(remote)

def dependencyToModule(dependency: Dependency): coursier.Dependency =
coursier.Dependency(
Module(dependency.groupId, dependency.artifactId),
coursier.Dependency.of(
Module(Organization(dependency.groupId), ModuleName(dependency.artifactId)),
dependency.version
)

def resolveArtifacts(remotes: Seq[Remote], dependencies: Seq[Dependency]): Task[Resolution] = {
val resolution = Resolution(dependencies.map(dependencyToModule).toSet)
val repositories: Seq[Repository] = Cache.ivy2Local +: remotes.map(remoteToRepository)
val fetch = Fetch.from(repositories, Cache.fetch())
resolution.process.run(fetch)
val cache: FileCache[F] = FileCache[F].noCredentials

def resolveArtifacts(remotes: Seq[Remote], dependencies: Seq[Dependency]): F[Resolution] = {
Resolve[F](cache)
.addDependencies(dependencies.map(dependencyToModule): _*)
.addRepositories(remotes.map(remoteToRepository): _*)
.addRepositories(coursier.LocalRepositories.ivy2Local)
.io
}

def fetchArtifacts(
remotes: Seq[Remote],
dependencies: Seq[Dependency]): Task[coursier.FileError \/ List[File]] =
dependencies: Seq[Dependency]): F[Either[ArtifactError, List[File]]] =
for {
resolution <- resolveArtifacts(remotes, dependencies)
artifacts <- Task.gatherUnordered(
resolution.artifacts.map(Cache.file(_).run)
)
} yield artifacts.sequenceU
resolution <- resolveArtifacts(remotes, dependencies)
gatheredArtifacts <- resolution.artifacts().toList.traverse(cache.file(_).run)
artifacts = gatheredArtifacts.foldRight(Right(Nil): Either[ArtifactError, List[File]]) {
case (Right(file), acc) => acc.map(file :: _)
case (Left(ae), _) => Left(ae)
}
} yield artifacts

def createEval(jars: Seq[File]) = {
new Eval(jars = jars.toList) {
Expand Down Expand Up @@ -124,22 +130,29 @@ class Evaluator(timeout: FiniteDuration = 20.seconds)(
code: String,
remotes: Seq[Remote] = Nil,
dependencies: Seq[Dependency] = Nil
): Task[EvalResult[T]] = {
): F[EvalResult[T]] = {
for {
allJars <- fetchArtifacts(remotes, dependencies)
result <- allJars match {
case \/-(jars) =>
Task({
evaluate(code, jars)
}).timed(timeout)
.handle({
case err: TimeoutException => Timeout[T](timeout)
})
case -\/(fileError) =>
Task.now(UnresolvedDependency(fileError.describe))
case Right(jars) =>
val fallback: EvalResult[T] = Timeout[T](timeout)
val success: F[EvalResult[T]] =
timeoutTo(F.delay { evaluate(code, jars) }, timeout, fallback)
success
case Left(fileError) =>
val failure: F[EvalResult[T]] = F.pure(UnresolvedDependency[T](fileError.describe))
failure
}
} yield result
}

def timeoutTo[A](fa: F[A], after: FiniteDuration, fallback: A): F[A] = {

Concurrent[F].race(fa, T.sleep(after)).flatMap {
case Left(a) => a.pure[F]
case Right(_) => fallback.pure[F]
}
}
}

/**
Expand Down Expand Up @@ -170,7 +183,7 @@ private class StringCompiler(
val settings = StringCompiler.this.settings
val messages = new scala.collection.mutable.ListBuffer[List[String]]

def display(pos: Position, message: String, severity: Severity) {
def display(pos: Position, message: String, severity: Severity) = {
severity.count += 1
val severityName = severity match {
case ERROR => "error: "
Expand All @@ -193,19 +206,19 @@ private class StringCompiler(
})
}

def displayPrompt {
def displayPrompt = {
// no.
}

override def reset {
override def reset = {
super.reset
messages.clear()
}
}

val global = new Global(settings, reporter)

def reset() {
def reset() = {
targetDir match {
case None => {
output.asInstanceOf[VirtualDirectory].clear()
Expand Down Expand Up @@ -239,7 +252,7 @@ private class StringCompiler(
/**
* Compile scala code. It can be found using the above class loader.
*/
def apply(code: String) {
def apply(code: String) = {
// if you're looking for the performance hit, it's 1/2 this line...
val compiler = new global.Run
val sourceFiles = List(new BatchSourceFile("(inline)", code))
Expand Down Expand Up @@ -366,7 +379,7 @@ class Eval(target: Option[File] = None, jars: List[File] = Nil) {
* Check if code is Eval-able.
* @throws CompilerException if not Eval-able.
*/
def check(code: String) {
def check(code: String) = {
val id = uniqueId(code)
val className = "Evaluator__" + id
val wrappedCode = wrapCodeInClass(className, code)
Expand Down
Loading

0 comments on commit a777e75

Please sign in to comment.