Require header across routes without giving up 404 - scala

I’d like to require a header in my akka-http routes and can do so via
val route = headerValueByName("foo") { foo =>
pathPrefix("path") {
get {
...
} ~ ...
}
}
However, now any requests that don't match a path will get rejected with 400 (missing header) and not 404.
Is there a neat way to get around this without repeatedly moving headerValueByName after the path matchers?
That is, is there a way to only apply an outer directive ( headerValueByName) and its rejections if the inner path and method matchers are successful?

You don't specify what you want to do in case the header is not specified, so I'll asume you want to return 400 (Bad request).
A possible solution is to use the optionalHeaderValueByName directive and then complete the request with the specified error, for example:
val route = optionalHeaderValueByName("foo") { optionalHeader =>
optionalHeader map { header =>
// rest of your routes
} getOrElse complete(StatusCodes.BadRequest)
}

Related

Lagom - Add header to response in service call composition

i want to write code which will refresh cookie (via set-cookie http header) in Lagom.
For clarification Cookie is an encoded string (for example AES).
Lets take lagom service call composition for authentication from Implementing services and edit it
def authenticated[Request, Response](
serviceCall: User => ServerServiceCall[Request, Response]
) = ServerServiceCall.composeAsync { requestHeader =>
//Get cookie from header and decode it
val cookie = decodeCookie(requestHeader)
//Get user based on cookie decode function
val userLookup = getUser(cookie)
userLookup.map {
case Some(user) =>
serviceCall(user)
case None => throw Forbidden("User must be authenticated")
}
}
It is possible to manipulate serviceCall(user) response headers?
I tried something like this:
serviceCall( employee ).handleResponseHeader { case (responseHeader, response) =>
responseHeader.withHeader("Set-Cookie",encodeCookie("NewCookieStringExample")) // Add header
response
}
But function handleResponseHeader requires only response[T] as result and header would not be changed because is immutable.
I know that i can pass cookie to serviceCall(user) and in every service call implementation return tuple with ResponseHeader and Response but this will impact all endpoints and will add much more code.
Use of HeaderFilter is possible too but this would decode and encode cookie twice in one request (in header filter and in authenticated service call composition )
Any tips?
You can return the modified Header and the Response as a Tuple:
serviceCall( employee ).handleResponseHeader((responseHeader, response) => {
val modifiedHeader = responseHeader.withHeader("Set-Cookie",encodeCookie("NewCookieStringExample")) // Add header
(modifiedHeader, response)
})
By the way, The provided example does not compile for me. composeAsync wants a Future[ServerServiceCall[...]].

Akka: Cancel Routing Due To Incorrect Query Parameters

So I have a route structure something like this
pathPrexix("root"){
concat {
path("path") {
get {
parameters("someId".as[String], 'fixedValue ! "requiredValue") { params =>
}
}
},
path(Segment) { extractedValue =>
.....
}
}
}
If the user ends a request to the /root/path endpoint with the incorrect query parameters (either someId missing or fixedValue not equal to value) then the request will be routed further on to the next route, root/Segment. extractedValue would in this case be path which would fail and send the user back error handled by the second route.
The preferred behaviour would be to tell the user that they either missed a query parameters or that the query parameter must be one of the given values. Is there any way to make sure that happen?
If I move the second path above the first, it will capture all requests sent.
You just need to complete with an appropriate error code if the get does not match:
path("path") {
concat(
get {
parameters("someId".as[String], 'fixedValue ! "requiredValue") { params =>
}
},
complete(StatusCodes.NotFound)
)
},
You could put additional information in the reply message, but it would be non-standard and therefore would require the client to be aware of it.

How do I access the full path of a request in Akka HTTP request?

In some contexts I can match on the remaining path using a PathDirective to get the information I need about the path. For example, when route below is directly bound and handled by Akka HTTP, every request will echo back the requested path as desired.
val route =
path(Remaining) { path =>
complete(path)
}
However, when the above route is combined elsewhere in the application, the path variable above may only hold part of the requested path not giving the desired results.
For example if the actual bound route is be,
val actualRoute = pathPrefix("echo") { route }
The "echo/" part of the overall path will be missing from the response given to the user.
How can the complete path be reliably accessed?
Directives extractMatchedPath and extractUnmatchedPath let you access the path without matching the path like you path directive does above. These two can be combined to construct the full path:
val route =
extractMatchedPath { matched =>
extractUnmatchedPath { unmatched =>
complete((matched.dropChars(1) ++ unmatched).toString)
}
}
However it is probably cleaner just to extract the Path from the URI directly:
val route =
extractUri { uri =>
complete(uri.toRelative.path.dropChars(1).toString)
}
Note that in both cases we needed to call .dropChars(1) to drop the initial forward slash and be consistent with the output you got using the path directive.

Akka HTTP set response header based on result of Future

I'm designing a REST service using Akka-HTTP 2.0-M2 and have come across a situation where I'd like to supply additional headers which are dependent upon the reply of the queried Actor.
Currently, I have the following...
val route = {
path("oncologist") {
get {
parameters('active.as[Boolean].?, 'skip.as[Int].?, 'limit.as[Int].?).as(GetAllOncologists) {
req =>
complete {
(oncologistActor ? req).mapTo[OncologistList]
}
}
}
}
While this is returning without issue. I'd like to move some of the properties of OncologistList into the response header rather than returning them in the body. Namely, I'm returning total record counts and offset and I would like to generate a previous and next URL header value for use by the client. I'm at a loss on how to proceed.
I think you can use the onComplete and respondWithHeaders directives to accomplish what you want. The onComplete directive works with the result of a Future which is exactly what ask (?) will return. Here is an example using a case class like so:
case class Foo(id:Int, name:String)
And a simple route showing onComplete like so:
get{
parameters('active.as[Boolean].?, 'skip.as[Int].?, 'limit.as[Int].?).as(GetAllOncologists) { req =>
val fut = (oncologistActor ? req).mapTo[Foo]
onComplete(fut){
case util.Success(f) =>
val headers = List(
RawHeader("X-MyObject-Id", f.id.toString),
RawHeader("X-MyObject-Name", f.name)
)
respondWithHeaders(headers){
complete(StatusCodes.OK)
}
case util.Failure(ex) =>
complete(StatusCodes.InternalServerError )
}
}
}
So if we get a successful result from the ask on oncologistActor we can then leverage the respondWithHeaders to add some custom headers to the response. Hopefully this is what you were looking for.

Spray routing works for single slash but nothing else

So i have asked about this before and have changed a lot of code around.
Spray Routing Doesn't match anything
Now I am executing my functions that return HTTPresponses insided detach() blocks so that i dont block. These then are completed and return to the client, but I still can't seem to get my routing to work.
In my tests, a request to a single slash works fine, but anything else, such as this create user path shown below fails. I can't seem to figure out why, and spray routing uses so many constructs I'm having a hard time figuring out how the system works well enough to find out whats happening.
I tried inserting logRequest blocks around certain paths thinking that might show me whats happening, but none of them seem to get hit. Any help would be greatly appreciated.
val route: Route = {
host("fakebook.com", "www.fakebook.com") {
pathSingleSlash {
complete("pong")
} ~
pathPrefix("users") { req =>
path("newuser") {
put {
detach() {
complete(genericPut(CreateUser(req.request)))
}
}
} ~
... rest of routing
And here is what my scalatests look like, the simple Put passes, but the put with newuser doesn't
val createUserSuccessRequest = Put(Uri("http://www.fakebook.com/users/newuser") withQuery(F_User.lastNameString -> "Doe", F_User.firstNameString -> "John", F_User.bioString -> "i like foobar",
F_User.ageString -> "42", F_User.dobString -> dateFormatter.format(new Date(1970 - 1900, 5, 7))))
"The FakeBook route" when {
"When sending a single slash request" should {
"respond with a simple pong" in {
Get() ~> logRequestResponse("plain get final request and response")(sealRoute(route)) ~> check {
assert(responseAs[String] == "pong")
}
}
}
"Running route to create a user with PUT" should {
"forward a message to the backbone to create a new user" in {
createUserSuccessRequest ~> logRequest("create user final request and response"){sealRoute(route)} ~> check {
expectMsg(CreateUser(createUserSuccessRequest))
}
}
}
}
For anyone else trying to solve this issue:
a lot of these directives actually DONT extract anything, so having the lambda inputs i have like req => and req2 => will not work.
It turns out, spray routing is designed so that you never have to touch the RequestContext as I have done with my functions (which is why I try to access it). They extract only the useful data. Rather than do things as I should and change my function signatures, i am going to (for now) do a hotfix that has worked.
if you absolutely must have the requestcontext, so long as you don't break it somehow, you can extract it by making your own extraction directive like so
val extractRequestContext = extract(x => x) and wrap your code in that
I did this
path("somepath") {
detach() {
extractRequestContext { request => complete(someFunc(request)) }
}
}
In the future I should learn to use the DSL more correctly and extract what I need from the request context using directives and pass THOSE to the functions