I am building some custom directives with Spray and I find a problem:
When a route is rejected I thought that it was immediately completed with a rejection. However it is not and it tries to complete the inner route. For example, in this code:
path("users") {
post {
entity(as[User]){
user =>
directive_always_reject(){
println("Hello")
complete(200, "Hello")
}
}
}
}
The "hello" is always printed, even if the directive is rejected.
I always thought that when a route is rejected it does not continue to inner routes (or at least is how I understand it)
Did I misunderstand something with Spray or there is a bug somewhere in my code?
Thanks you!
[UPDATE 1]
An implementation of directive_always_reject() could be:
def directive_always_reject[T]() : Directive0 = {
reject(
new ValidationRejection("Some error")
)
}
[UPDATE 2]
I see that a solution could be put the println inside the complete function.
path("users") {
post {
entity(as[User]){
user =>
directive_always_reject(){
complete {
println("Hello")
"Hello"
}
}
}
}
}
However this solution does not apply to me because in my real code I pass the RequestContext to another actor who is the one that completes the request.
Related
I am trying to count rejects that were returned by RejectionHandler
I guess the way I doing this now is not the best one, or even incorrect. I am just trying to invoke the incremental method in my database, in each of the handled cases.
implicit def rejectionHandler: RejectionHandler =
RejectionHandler.newBuilder()
.handle {
case MissingCookieRejection(cookieName) =>
requestInfoEntry.incrementRjectedNum
complete(HttpResponse(BadRequest, entity = "No cookies, no service!!!"))
}
.handle {
case AuthorizationFailedRejection =>
requestInfoEntry.incrementRjectedNum
complete((Forbidden, "You're out of your depth!"))
}
.handle {
case ValidationRejection(msg, _) =>
requestInfoEntry.incrementRjectedNum
complete((InternalServerError, "That wasn't valid! " + msg))
}
.handleAll[MethodRejection] { methodRejections =>
requestInfoEntry.incrementRjectedNum//todo sideeffect ??
val names = methodRejections.map(_.supported.name)
complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
}
.handleNotFound {
requestInfoEntry.incrementRjectedNum
complete((NotFound, "Not here bldghad!"))
}
.result()
While I "visit my unfound page", Akka HTTP returns me a right response: "Not here bldghad!" every time I refresh browser on not existed web-page. But when I check my database, I see the only one increment. Can I do this way as I do at all? (I need to count successes too)
PS Maybe I need to work with status codes and do not use side effects. But what is the best place where I can do it? I have a lot of controllers and do not want to intercept this in every controller)
Can I globally intercept responses somewhere?
I've done it this way, hope it would be useful
def rejectionHandlerWithCounter: RejectionHandler = { (rejections: Seq[Rejection]) =>
requestInfoEntry.incrementRjectedNum
Some(complete((StatusCodes.Forbidden)))
}
How to verify auth before handle body request?
I'm using vertx:
vertxVersion = '3.8.3'
implementation "io.vertx:vertx-core:$rootProject.vertxVersion"
implementation "io.vertx:vertx-web:$rootProject.vertxVersion"
implementation "io.vertx:vertx-lang-kotlin:$rootProject.vertxVersion"
implementation "io.vertx:vertx-lang-kotlin-coroutines:$rootProject.vertxVersion"
implementation "io.vertx:vertx-mongo-client:$rootProject.vertxVersion"
implementation "io.vertx:vertx-auth-mongo:$rootProject.vertxVersion"
implementation "io.vertx:vertx-auth-jwt:$rootProject.vertxVersion"
I want to verify auth before handle body request. But I got error java.lang.IllegalStateException: Request has already been read
Reproduce by use delay on suspend function:
router.handler { context ->
launch {
context.request().setExpectMultipart(true)
delay(100) //This line is sample for a verify auth process
context.next()
}
}
.handler {context ->
println("2")
context.request()
.handler {
b -> println("buff ${b.length()}")
}
.endHandler {
println("end handle")
context.success("ok")
}
}.baseHandle(
fn
).failureHandler {
println("fail: ${it.failure()}")
it.error()
}
When run delay(100) (this's sample for a verify process), I got the error above. If I comment delay(100), It's will be working fine.
This happens because by the time you auhenticated the request, the content has kept arriving and has been dropped.
You need to invoke context.request().pause() in you first handler and then context.request().resume() when you're ready.
In most cases though it's easier to let the BodyHandler manage payload for you.
Finally, I did solve it.
My router is working with the flows:
router.post("/api/upload/file")
.baseHandle { checkAuthorization(it) }
.handleFileUpload { updateFileOnItem(it) }
And Following step:
fun checkAuthorization(context: RoutingContext) {
val request = context.request()
val tkCookie = request.getCookie("user")
...do something to verify user permission
request.pause()
context.next()
context.request().resume()
}
Next:
fun updateFileOnItem(context: RoutingContext) {
val file = context.fileUploads()
...do something
}
It's working with me. Hope it can be help you. Thanks!
Here is my simple routing application:
object Main extends App with SimpleRoutingApp {
implicit val system = ActorSystem("my-system")
startServer(interface = "0.0.0.0", port = System.getenv("PORT").toInt) {
import format.UsageJsonFormat._
import spray.httpx.SprayJsonSupport._
path("") {
get {
complete("OK")
}
} ~
path("meter" / JavaUUID) {
meterUUID => pathEnd {
post {
entity(as[Usage]) {
usage =>
// execute some logic asynchronously
// do not wait for the result
complete("OK")
}
}
}
}
}
}
What I want to achieve is to execute some logic asynchronously in my path directive, do not wait for the result and return immediately HTTP 200 OK.
I am quite new to Scala and spray and wondering if there is any spray way to solve this specific problem. Otherwise I would go into direction of creating Actor for every request and letting it to do the job. Please advice.
There's no special way of handling this in spray: simply fire your async action (a method returning a Future, a message sent to an actor, whatever) and call complete right after.
def doStuffAsync = Future {
// literally anything
}
path("meter" / JavaUUID) { meterUUID =>
pathEnd {
post {
entity(as[Usage]) { usage =>
doStuffAsync()
complete("OK")
}
}
}
}
Conversely, if you need to wait for an async action to complete before sending the response, you can use spray-specific directives for working with Futures or Actors.
Helo,
at the beginning i wold like to apologize for my english :)
akka=2.3.6
spray=1.3.2
scalatest=2.2.1
I encountered strange behavior of teting routes, which asks actors in handleWith directive,
I've route with handleWith directive
pathPrefix("firstPath") {
pathEnd {
get(complete("Hello from this api")) ~
post(handleWith { (data: Data) =>{ println("receiving data")
(dataCalculator ? data).collect {
case Success(_) =>
Right(Created -> "")
case throwable: MyInternalValidatationException =>
Left(BadRequest -> s"""{"${throwable.subject}" : "${throwable.cause}"}""")
}
}})
}
}
and simple actor wchich always responds when receive object Data and has own receive block wrapped in LoggingReceive, so I should see logs when message is receiving by actor
and i test it using (I think simple code)
class SampleStarngeTest extends WordSpec with ThisAppTestBase with OneInstancePerTest
with routeTestingSugar {
val url = "/firstPath/"
implicit val routeTestTimeout = RouteTestTimeout(5 seconds)
def postTest(data: String) = Post(url).withJson(data) ~> routes
"posting" should {
"pass" when {
"data is valid and comes from the identified user" in {
postTest(correctData.copy(createdAt = System.currentTimeMillis()).asJson) ~> check {
print(entity)
status shouldBe Created
}
}
"report is valid and comes from the anonymous" in {
postTest(correctData.copy(createdAt = System.currentTimeMillis(), adid = "anonymous").asJson) ~> check {
status shouldBe Created
}
}
}
}
}
and behavior:
When I run either all tests in package (using Intellij Idea 14 Ultimate) or sbt test I encounter the same results
one execution -> all tests pass
and next one -> not all pass, this which not pass I can see:
1. fail becouse Request was neither completed nor rejected within X seconds ( X up tp 60)
2. system console output from route from line post(handleWith { (data: Data) =>{ println("receiving data"), so code in handleWith was executed
3. ask timeout exception from route code, but not always (among failed tests)
4. no logs from actor LoggingReceive, so actor hasn't chance to respond
5. when I rerun teststhe results are even different from the previous
Is there problem with threading? or test modules, thread blocking inside libraries? or sth else? I've no idea why it isn't work :(
Is there any difference between these two ways of completing a failed Future? If so, which way is considered to be more "correct"?
Calling Promise.failure:
def functionThatFinishesLater: Future[String] = {
val myPromise = Promise[String]
Future {
// Do something that might fail
if (failed) {
myPromise.failure(new RuntimeException("message")) // complete with throwable
} else {
myPromise.success("yay!")
}
} (aDifferentExecutionContext)
myPromise.future
}
Or just throwing an exception
def functionThatFinishesLater: Future[String] = {
val myPromise = Promise[String]
Future {
// Do something that might fail
if (failed) {
throw new RuntimeException("message") // throw the exception
} else {
myPromise.success("yay!")
}
} (aDifferentExecutionContext)
myPromise.future
}
It looks to me like you're mixing paradigms. A Promise is an imperative way of completing a Future, but a Future can also be made completed by wrapping the computation in a Future constructor. You're doing both, which is probably not what you want. The second statement in both code fragments is of type Future[Promise[String]], and I'm almost certain you really want just Future[String].
If you're using using the Future.apply constructor, you should just treat the value produced as the Future, rather than using it to resolve a separate Promise value:
val myFuture = Future {
// Do some long operation that might fail
if (failed) {
throw new RuntimeException("message")
} else {
"yay!"
}
}
The way to use the Promise is to create the Promise, give its Future to some other piece of code that cares, and then use .success(...) or .failure(...) to complete it after some long running operation. So to recap, the big difference is that Future has to wrap the whole computation, but you can pass a Promise around and complete it elsewhere if you need to.