Skip to content
Snippets Groups Projects
Commit 804e54fb authored by Joel Cavat's avatar Joel Cavat
Browse files

Initial secure commit

parents
No related branches found
No related tags found
No related merge requests found
*.class
.idea
target
lazy val akkaHttpVersion = "10.1.7"
lazy val akkaVersion = "2.5.21"
fork in run := true
lazy val root = (project in file(".")).
settings(
inThisBuild(List(
organization := "com.example",
scalaVersion := "2.12.7"
)),
name := "My Akka HTTP Project",
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-xml" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test,
"com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test,
"org.scalatest" %% "scalatest" % "3.0.5" % Test
)
)
sbt.version=1.2.0
addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2")
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
package com.example
import com.example.UserRegistryActor.ActionPerformed
//#json-support
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json.DefaultJsonProtocol
trait JsonSupport extends SprayJsonSupport {
// import the default encoders for primitive types (Int, String, Lists etc)
import DefaultJsonProtocol._
implicit val userJsonFormat = jsonFormat3(User)
implicit val usersJsonFormat = jsonFormat1(Users)
implicit val actionPerformedJsonFormat = jsonFormat1(ActionPerformed)
}
//#json-support
package com.example
//#quick-start-server
import scala.concurrent.{ Await, ExecutionContext, Future }
import scala.concurrent.duration.Duration
import scala.util.{ Failure, Success }
import akka.actor.{ ActorRef, ActorSystem }
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer
//#main-class
object QuickstartServer extends App with UserRoutes {
// set up ActorSystem and other dependencies here
//#main-class
//#server-bootstrapping
implicit val system: ActorSystem = ActorSystem("helloAkkaHttpServer")
implicit val materializer: ActorMaterializer = ActorMaterializer()
implicit val executionContext: ExecutionContext = system.dispatcher
//#server-bootstrapping
val userRegistryActor: ActorRef = system.actorOf(UserRegistryActor.props, "userRegistryActor")
//#main-class
// from the UserRoutes trait
lazy val routes: Route = userRoutes
//#main-class
//#http-server
val serverBinding: Future[Http.ServerBinding] = Http().bindAndHandle(routes, "localhost", 8080)
serverBinding.onComplete {
case Success(bound) =>
println(s"Server online at http://${bound.localAddress.getHostString}:${bound.localAddress.getPort}/")
case Failure(e) =>
Console.err.println(s"Server could not start!")
e.printStackTrace()
system.terminate()
}
Await.result(system.whenTerminated, Duration.Inf)
//#http-server
//#main-class
}
//#main-class
//#quick-start-server
package com.example
//#user-registry-actor
import akka.actor.{ Actor, ActorLogging, Props }
//#user-case-classes
final case class User(name: String, age: Int, countryOfResidence: String)
final case class Users(users: Seq[User])
//#user-case-classes
object UserRegistryActor {
final case class ActionPerformed(description: String)
final case object GetUsers
final case class CreateUser(user: User)
final case class GetUser(name: String)
final case class DeleteUser(name: String)
def props: Props = Props[UserRegistryActor]
}
class UserRegistryActor extends Actor with ActorLogging {
import UserRegistryActor._
var users = Set.empty[User]
def receive: Receive = {
case GetUsers =>
sender() ! Users(users.toSeq)
case CreateUser(user) =>
users += user
sender() ! ActionPerformed(s"User ${user.name} created.")
case GetUser(name) =>
sender() ! users.find(_.name == name)
case DeleteUser(name) =>
users.find(_.name == name) foreach { user => users -= user }
sender() ! ActionPerformed(s"User ${name} deleted.")
}
}
//#user-registry-actor
\ No newline at end of file
package com.example
import akka.actor.{ ActorRef, ActorSystem }
import akka.event.Logging
import scala.concurrent.duration._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.directives.Credentials
import akka.http.scaladsl.server.directives.Credentials.Provided
import akka.http.scaladsl.server.directives.MethodDirectives.delete
import akka.http.scaladsl.server.directives.MethodDirectives.get
import akka.http.scaladsl.server.directives.MethodDirectives.post
import akka.http.scaladsl.server.directives.RouteDirectives.complete
import akka.http.scaladsl.server.directives.PathDirectives.path
import scala.concurrent.Future
import com.example.UserRegistryActor._
import akka.pattern.ask
import akka.util.Timeout
//#user-routes-class
trait UserRoutes extends JsonSupport {
//#user-routes-class
// we leave these abstract, since they will be provided by the App
implicit def system: ActorSystem
lazy val log = Logging(system, classOf[UserRoutes])
// other dependencies that UserRoutes use
def userRegistryActor: ActorRef
// Required by the `ask` (?) method below
implicit lazy val timeout = Timeout(5.seconds) // usually we'd obtain the timeout from the system's configuration
def check(credentials: Credentials): Option[String] = credentials match {
case p @ Credentials.Provided(id) if id == "john" && p.verify("p4ssw0rd") => Some(id)
case _ => None
}
//#all-routes
//#users-get-post
//#users-get-delete
lazy val userRoutes: Route = Route.seal {
pathPrefix("users") {
authenticateBasic(realm = "secure site", check) { userName =>
concat(
//#users-get-delete
pathEnd {
concat(
get {
val users: Future[Users] =
(userRegistryActor ? GetUsers).mapTo[Users]
log.info(userName + " registered")
complete(users)
},
post {
entity(as[User]) { user =>
val userCreated: Future[ActionPerformed] =
(userRegistryActor ? CreateUser(user)).mapTo[ActionPerformed]
onSuccess(userCreated) { performed =>
log.info("Created user [{}]: {}", user.name, performed.description)
complete((StatusCodes.Created, performed))
}
}
})
},
//#users-get-post
//#users-get-delete
path(Segment) { name =>
concat(
get {
//#retrieve-user-info
val maybeUser: Future[Option[User]] =
(userRegistryActor ? GetUser(name)).mapTo[Option[User]]
rejectEmptyResponse {
complete(maybeUser)
}
//#retrieve-user-info
},
delete {
//#users-delete-logic
val userDeleted: Future[ActionPerformed] =
(userRegistryActor ? DeleteUser(name)).mapTo[ActionPerformed]
onSuccess(userDeleted) { performed =>
log.info("Deleted user [{}]: {}", name, performed.description)
complete((StatusCodes.OK, performed))
}
//#users-delete-logic
})
})
}
//#users-get-delete
}
//#all-routes
}
}
package com.example
//#user-routes-spec
//#test-top
import akka.actor.ActorRef
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{ Matchers, WordSpec }
//#set-up
class UserRoutesSpec extends WordSpec with Matchers with ScalaFutures with ScalatestRouteTest
with UserRoutes {
//#test-top
// Here we need to implement all the abstract members of UserRoutes.
// We use the real UserRegistryActor to test it while we hit the Routes,
// but we could "mock" it by implementing it in-place or by using a TestProbe()
override val userRegistryActor: ActorRef =
system.actorOf(UserRegistryActor.props, "userRegistry")
lazy val routes = userRoutes
//#set-up
//#actual-test
"UserRoutes" should {
"return no users if no present (GET /users)" in {
// note that there's no need for the host part in the uri:
val request = HttpRequest(uri = "/users")
request ~> routes ~> check {
status should ===(StatusCodes.OK)
// we expect the response to be json:
contentType should ===(ContentTypes.`application/json`)
// and no entries should be in the list:
entityAs[String] should ===("""{"users":[]}""")
}
}
//#actual-test
//#testing-post
"be able to add users (POST /users)" in {
val user = User("Kapi", 42, "jp")
val userEntity = Marshal(user).to[MessageEntity].futureValue // futureValue is from ScalaFutures
// using the RequestBuilding DSL:
val request = Post("/users").withEntity(userEntity)
request ~> routes ~> check {
status should ===(StatusCodes.Created)
// we expect the response to be json:
contentType should ===(ContentTypes.`application/json`)
// and we know what message we're expecting back:
entityAs[String] should ===("""{"description":"User Kapi created."}""")
}
}
//#testing-post
"be able to remove users (DELETE /users)" in {
// user the RequestBuilding DSL provided by ScalatestRouteSpec:
val request = Delete(uri = "/users/Kapi")
request ~> routes ~> check {
status should ===(StatusCodes.OK)
// we expect the response to be json:
contentType should ===(ContentTypes.`application/json`)
// and no entries should be in the list:
entityAs[String] should ===("""{"description":"User Kapi deleted."}""")
}
}
//#actual-test
}
//#actual-test
//#set-up
}
//#set-up
//#user-routes-spec
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment