i have a route which needs an access token in its header to grant access to it for that i have this working code
def accessProtectedResource: server.Route =
path("access-protected-resource") {
get {
bearerToken { token =>
token match {
case Some(tokenValue) =>
complete(OK, routeResponseMessage.getResponse(OK.intValue,ServerMessages.AUTH_PASS,JsObject.empty))
case None => reject(AuthorizationFailedRejection)
}
}
}
}
private def bearerToken: Directive1[Option[String]] =
for {
authBearerHeader <- optionalHeaderValueByType(classOf[Authorization]).map(extractBearerToken)
xAuthCookie <- optionalCookie("X-Authorization-Token").map(_.map(_.value))
} yield authBearerHeader.orElse(xAuthCookie)
private def extractBearerToken(authHeader: Option[Authorization]): Option[String] =
authHeader.collect {
case Authorization(OAuth2BearerToken(token)) => token
}
when i hit the route through postman in the Authorization tab i selected the type to Bearer Token and add the token and send the request and everything works fine now i want to unit test this route
for this i am looking at this
but i am confused how can i add the header in a proper way in my unit test here is my code
"pass route /access-protected-resource" in {
routeResponseMessage.getResponse(OK.intValue, ServerMessages.AUTH_PASS, JsObject.empty)
val originHeader = Authorization(OAuth2BearerToken("accessTokenString"))
Get("http://0.0.0.0:8083/get-user-token") ~> originHeader ~> authenticationController.route ~> check {
}
}
but my route is getting rejected
- pass route /access-protected-resource *** FAILED ***
[info] Request was rejected with rejection MethodRejection(HttpMethod(POST)) (CheckValidUserTokenExistsTest.scala:76)
how to do this correctly ?
How about using addCredential and testing only bearerToken method wrapped with Route.seal like this(https://github.com/ItoYo16u/scala-usage-playground/blob/master/src/test/scala/web/akka/JWTDirectivesSpec.scala)?
i have the following akka http rejection handling code taken from https://doc.akka.io/docs/akka-http/current/routing-dsl/rejections.html
val message = "The requested resource could not be found."
implicit def myRejectionHandler = RejectionHandler.newBuilder()
.handleNotFound {
complete(HttpResponse(NotFound
,entity = HttpEntity(ContentTypes.`application/json`, s"""{"rejection": "$message"}"""
)))
}.result()
val route: Route = handleRejections(myRejectionHandler) {
handleExceptions(myExceptionHandler) {
concat(
path("event-by-id") {
get {
parameters('id.as[String]) {
id =>
complete("id")
}
}
}
,
post {
path("create-event") {
entity(as[Event]) {
event =>
complete(OK, "inserted")
}
}
}
)
}
}
}
val bindingFuture = Http().bindAndHandle(route, hostName, port)
when i hit localhost:8080/random
i got the message
HTTP method not allowed, supported methods: POST
and when i select POST and hit localhost:8080/random
i got the message
{
"rejection": "The requested resource could not be found."
}
why i did not get the same message when my route request was GET ?
in the docs the handleNotFound was working with GET request https://doc.akka.io/docs/akka-http/current/routing-dsl/rejections.html
This is happens, probably because of order of directives, you are using: in your configuration if incoming request does not match with event-by-id URL path, then it goes to the next handler, which expects that request should have POST method first of all, because post directive goes first, before path("create-event").
What you can try to do is change directives order to the next one, for second route:
path("create-event") {
post {
entity(as[Event]) { event =>
complete(OK, "inserted")
}
}
}
Hope this helps!
Question about akka-http server. I have a top-level mapResponse which wraps all other routes. This mapResponse wraps all responses in the json. And recently I found that sometimes my server returns 503 error with plain-text explanation. I found that the reason is akka.http.server.request-timeout config value - it was too small. But why I didn't catch these responses with the top-level mapResponse? I found that this 503 response is created in HttpServerBluePrint.TimeoutAccessImpl.apply and it somehow ignores my mapResponse.
Here is the minimal reproduction code. I wrap all my routes in logAndReturn200 function. I expect that all my routes will return StatusCodes.OK. But /bar returns timeout error.
val logAndReturn200 = mapResponse { response =>
println(response)
HttpResponse(StatusCodes.OK)
}
val route = {
logAndReturn200 {
get {
path("foo") {
complete("OK")
} ~
path("bar") {
val promise = Promise[Unit]()
actorSystem.scheduler.scheduleOnce(10.seconds) {
promise.success(())
}
onSuccess(promise.future) {
complete("OK")
}
}
}
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
I have an Akka HTTP daemon. Assume that I want to receive some client data in JSON format and save it into a database asynchronously. I wrote a route in a POST branch:
path("product") {
entity(as[String]) { json =>
val saveFuture: Future[Unit] = Serialization.read[Product](json).save()
complete("")
}
}
I've found that complete can be put into an onSuccess statement like:
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
}
But I can't understand how to glue them together.
You can nest the directives:
path("product") {
entity(as[String]) { json =>
val saveFuture: Future[Unit] = Serialization.read[Product](json).save()
onSuccess(saveFuture) {
complete("json was saved")
}
}
}
I don't want to explicitly write:
options { ... }
for each entry point / path in my Spray route. I'd like to write some generic code that will add OPTIONS support for all paths. It should look at the routes and extract supported methods from them.
I can't paste any code since I don't know how to approach it in Spray.
The reason I'm doing it is I want to provide a self discoverable API that adheres to HATEOAS principles.
The below directive will be able to catch a rejected request, check if it is a option request, and return:
The CORS headers, to support CORS (this directive removes ALL cors protection, beware!!!!!)
The Allow headers, to give the peer a list of available methods
Try to understand the below snippet and adjust it where necessary. You should prefer to deliver as much information as possible, but if you only want to return the Allowed methods I suggest you cut out the rest :).
import spray.http.{AllOrigins, HttpMethods, HttpMethod, HttpResponse}
import spray.http.HttpHeaders._
import spray.http.HttpMethods._
import spray.routing._
/**
* A mixin to provide support for providing CORS headers as appropriate
*/
trait CorsSupport {
this: HttpService =>
private val allowOriginHeader = `Access-Control-Allow-Origin`(AllOrigins)
private val optionsCorsHeaders = List(
`Access-Control-Allow-Headers`(
"Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, " +
"Referer, User-Agent"
),
`Access-Control-Max-Age`(60 * 60 * 24 * 20) // cache pre-flight response for 20 days
)
def cors[T]: Directive0 = mapRequestContext {
context => context.withRouteResponseHandling {
// If an OPTIONS request was rejected as 405, complete the request by responding with the
// defined CORS details and the allowed options grabbed from the rejection
case Rejected(reasons) if (
context.request.method == HttpMethods.OPTIONS &&
reasons.exists(_.isInstanceOf[MethodRejection])
) => {
val allowedMethods = reasons.collect { case r: MethodRejection => r.supported }
context.complete(HttpResponse().withHeaders(
`Access-Control-Allow-Methods`(OPTIONS, allowedMethods :_*) ::
allowOriginHeader ::
optionsCorsHeaders
))
}
} withHttpResponseHeadersMapped { headers => allowOriginHeader :: headers }
}
}
Use it like this:
val routes: Route =
cors {
path("hello") {
get {
complete {
"GET"
}
} ~
put {
complete {
"PUT"
}
}
}
}
Resource: https://github.com/giftig/mediaman/blob/22b95a807f6e7bb64d695583f4b856588c223fc1/src/main/scala/com/programmingcentre/utils/utils/CorsSupport.scala
I did it like this:
private val CORSHeaders = List(
`Access-Control-Allow-Methods`(GET, POST, PUT, DELETE, OPTIONS),
`Access-Control-Allow-Headers`("Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, User-Agent"),
`Access-Control-Allow-Credentials`(true)
)
def respondWithCORS(origin: String)(routes: => Route) = {
val originHeader = `Access-Control-Allow-Origin`(SomeOrigins(Seq(HttpOrigin(origin))))
respondWithHeaders(originHeader :: CORSHeaders) {
routes ~ options { complete(StatusCodes.OK) }
}
}
val routes =
respondWithCORS(config.getString("origin.domain")) {
pathPrefix("api") {
// ... your routes here
}
}
So every OPTION request to any URL with /api prefix returns 200 code.
Update: added Access* headers.
Methinks options is generic enough, you can use it as:
path("foo") {
options {
...
}
} ~
path("bar") {
options {
...
}
}
or as this:
options {
path("foo") {
...
} ~
path("bar") {
...
}
}