Last active
January 28, 2021 14:01
-
-
Save jeroenr/5261fa041d592f37cd80 to your computer and use it in GitHub Desktop.
Akka HTTP API with CORS headers and custom Media types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import akka.actor.ActorSystem | |
import akka.http.scaladsl.Http | |
import akka.stream.ActorMaterializer | |
object Boot extends App with Service with CorsSupport { | |
override implicit val system = ActorSystem() | |
override implicit val executor = system.dispatcher | |
override implicit val materializer = ActorMaterializer() | |
Http().bindAndHandle(corsHandler(routes), "0.0.0.0", 1337) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import akka.http.scaladsl.model.HttpMethods._ | |
import akka.http.scaladsl.model.{StatusCodes, HttpResponse} | |
import akka.http.scaladsl.model.headers._ | |
import akka.http.scaladsl.server.Directives._ | |
import akka.http.scaladsl.server.{Directive0, Route} | |
import com.typesafe.config.ConfigFactory | |
trait CorsSupport { | |
lazy val allowedOrigin = { | |
val config = ConfigFactory.load() | |
val sAllowedOrigin = config.getString("cors.allowed-origin") | |
HttpOrigin(sAllowedOrigin) | |
} | |
//this directive adds access control headers to normal responses | |
private def addAccessControlHeaders: Directive0 = { | |
respondWithHeaders( | |
`Access-Control-Allow-Origin`(allowedOrigin), | |
`Access-Control-Allow-Credentials`(true), | |
`Access-Control-Allow-Headers`("Authorization", "Content-Type", "X-Requested-With") | |
) | |
} | |
//this handles preflight OPTIONS requests. | |
private def preflightRequestHandler: Route = options { | |
complete(HttpResponse(StatusCodes.OK).withHeaders(`Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE))) | |
} | |
def corsHandler(r: Route) = addAccessControlHeaders { | |
preflightRequestHandler ~ r | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
libraryDependencies ++= { | |
val akkaV = "2.3.12" | |
val akkaStreamV = "1.0" | |
Seq( | |
"com.typesafe.akka" %% "akka-actor" % akkaV, | |
"com.typesafe.akka" %% "akka-stream-experimental" % akkaStreamV, | |
"com.typesafe.akka" %% "akka-http-core-experimental" % akkaStreamV, | |
"com.typesafe.akka" %% "akka-http-experimental" % akkaStreamV, | |
"com.typesafe.akka" %% "akka-http-spray-json-experimental" % akkaStreamV | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import akka.actor.ActorSystem | |
import akka.event.LoggingAdapter | |
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ | |
import akka.http.scaladsl.marshalling.{ToEntityMarshaller, Marshaller} | |
import akka.http.scaladsl.model._ | |
import akka.http.scaladsl.server.Directives._ | |
import akka.stream.Materializer | |
import com.oxyme.monitoring.HealthChecker | |
import com.oxyme.monitoring.model.{Unhealthy, Healthy} | |
import com.typesafe.config.{ConfigFactory, Config} | |
import akka.http.scaladsl.model.StatusCodes._ | |
import spray.json._ | |
import spray.json.DefaultJsonProtocol | |
import scala.concurrent.ExecutionContextExecutor | |
case class Person(name: String, age: Int) | |
case class Persons(total: Int, data: Iterable[Person]) | |
trait Protocols extends DefaultJsonProtocol { | |
implicit val personFormat = jsonFormat2(Person.apply) | |
implicit val personsFormat = jsonFormat2(Persons.apply) | |
val `application/vnd.persons.v1+json` = | |
MediaType.custom("application/vnd.persons.v1+json", MediaType.Encoding.Fixed(HttpCharsets.`UTF-8`)) | |
implicit val personsMarshaller: ToEntityMarshaller[Persons] = Marshaller.opaque { persons => | |
HttpEntity(ContentType(`application/vnd.persons.v1+json`, HttpCharsets.`UTF-8`), persons.toJson.compactPrint) | |
} | |
} | |
trait Service extends Protocols { | |
implicit val system: ActorSystem | |
implicit val materializer: Materializer | |
// declared here because it needs Materializer as opposed to Marshaller | |
implicit val personsUnmarshaller = sprayJsonUnmarshaller[Persons].forContentTypes( | |
`application/vnd.persons.v1+json`, | |
`application/json` | |
) | |
val routes = { | |
path("persons"){ | |
get { | |
complete { | |
Persons(3, Seq(Person("jeroen", 28), Person("harry", 12), Person("john", 31))) | |
} | |
} | |
} | |
} | |
} |
@jrudolph good point, thnx 👍
Good demo! It works well for me.
Perfect! Exactly what i was looking for.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There's also the
respondWithHeaders
directive which is slightly more straight-forward thanmapResponseHeaders
.