I have (formerly) REST spray.io webservice. Now, I need to generate SESSIONID in one of my methods to be used with some other methods. And I want it to be in the response header.
Basically, I imagine logic like the following:
path("/...") {
get {
complete {
// some logic here
// .....
someResult match {
case Some(something) =>
val sessionID = generateSessionID
session(sessionID) = attachSomeData(something)
// here I need help how to do my imaginary respond with header
[ respond-with-header ? ]("X-My-SessionId", sessionID) {
someDataMarshalledToJSON(something)
}
case None => throw .... // wrapped using error handler
}
}
}
}
But, it doesn't work inside complete, I mean respondWithHeader directive. I need an advice.
There is a respondWithHeader directive in Spray. Here is official doc and example of how you could use it:
def respondWithSessionId(sessionID: String) =
respondWithHeader(RawHeader("X-My-SessionId", sessionID))
path("/...") {
get {
// some logic here
// .....
sessionIDProvider { sessionID =>
respondWithMediaType(`application/json`) { // optionally add this if you want
respondWithSessionId(sessionID) {
complete(someDataMarshalledToJSON(something))
}
}
}
}
}
Related
I'm new to scala, and I'm trying to figure out how to add to the existing routes we have so that if a certain path is hit, we evaluate the headers by checking for the existence of some values and whether or not they equal some accepted values. If it succeeds, we get some String out of the headers and pass it on, otherwise we should not continue routing and return some failure.
/abc -> don't check headers
/abc/def -> check headers, return
pathPrefix("abc") {
path("def") { // want to ADD something here to check headers and send it into someMethod
get {
complete(HttpEntity(something.someMethod(someValue)))
}
} ~ path("gdi") {
get { ... etc}
}
}
Any ideas or dummy examples would be really helpful. I see some directives here to get stuff from the request, and the header (https://doc.akka.io/docs/akka-http/10.0.11/scala/http/routing-dsl/directives/header-directives/headerValue.html), but I don't understand how to chain directives in this way.
If I'm misunderstanding something, please help clarify! Thanks
Use headerValueByName, which looks for a specific header and rejects the request if that header isn't found:
get {
headerValueByName("MyHeader") { headerVal =>
complete(HttpEntity(something.someMethod(headerVal)))
}
}
To validate the header value if it exists:
get {
headerValueByName("MyHeader") { headerVal =>
if (isValid(headerVal)) // isValid is a custom method that you provide
complete(HttpEntity(something.someMethod(headerVal)))
else
complete((BadRequest, "The MyHeader value is invalid."))
}
}
isValid in the above example could look something like:
def isValid(headerValue: String): Boolean = {
val acceptedValues = Set("burrito", "quesadilla", "taco")
acceptedValues.contains(headerValue.toLowerCase)
}
I'm trying to make a general method for sending SOAP requests and getting responses. I'm programming using Groovy and I'm using the wslite library to help me out with SOAP. Here's a sample snippet for making a SOAP request and getting a response:
#Grab('com.github.groovy-wslite:groovy-wslite:1.1.2')
import wslite.soap.*
SOAPClient client = new SOAPClient('http://www.dneonline.com/calculator.asmx')
def response = client.send(SOAPAction: 'http://tempuri.org/Add') {
body {
Add(xmlns: 'http://tempuri.org/') {
intA(x)
intB(y)
}
}
}
By general, I meant being able to dynamically create a SOAP request (given certain information such as the service/method name, the parameters contained in the method, etc.) and obtain the SOAP response. I'm thinking something like this:
#Grab('com.github.groovy-wslite:groovy-wslite:1.1.2')
import wslite.soap.*
def getResponse(String clientURL, String action, String service, String serviceNamespace, Map parameters, ...) {
SOAPClient client = new SOAPClient(clientURL)
def response = client.send(SOAPAction: action) {
body {
"$service"(xmlns: serviceNameSpace) {
...
}
}
}
}
My problem lies in constructing the closure for the request body. Like, in example, if my method received a service Add, a serviceNamespace http://tempuri.org/, and a parameter map like so: [intA: x, intB: y]... how do I merge all of these so that I can construct this kind of closure:
Add(xmlns: 'http://tempuri.org/') {
intA(x)
intB(y)
}
I'm pretty much a newbie to Groovy, so don't be too harsh. If there's a better way to implement this concept of a general method, I would gladly like to hear it. The concept is similar to this. But I'd rather play with Map than a String. I'm not using Grails, really. Just plain Groovy.
In short, cfrick is correct:
[intA: x, intB: y].each{fn,arg -> delegate."$fn"(arg) }
How does it work?
An easy way to see how this works is to simulate it with a fake client class:
groovy.util.NodeBuilder
class Client {
def send(String action, Closure closure) {
closure.delegate = new NodeBuilder()
closure()
}
}
def client = new Client()
def response = client.send('http://tempuri.org/Add') {
body {
Add(xmlns: 'http://tempuri.org/') {
intA(1)
intB(2)
}
}
}
assert response.Add[0].#xmlns == 'http://tempuri.org/'
assert response.Add.intA.text() == '1'
assert response.Add.intB.text() == '2'
In the example above, the response object is created by Groovy's NodeBuilder. It's just a quick way to prototype something that processes the closure passed to Client.send().
With this testable code I'll try what cfrick suggested and validate that it works:
def doIt(String action, String service, String serviceNamespace, Map params) {
def client = new Client()
client.send(action) {
body {
"$service"(xmlns: serviceNamespace) {
params.each { method, argument ->
delegate."$method"(argument)
}
}
}
}
}
response = doIt('http://tempuri.org/Add', 'Add', 'http://tempuri.org/', [intA: 1, intB: 2])
assert response.Add[0].#xmlns == 'http://tempuri.org/'
assert response.Add.intA.text() == '1'
assert response.Add.intB.text() == '2'
Request Body
In addition, you can factor out the process of creating the request body:
def getRequestBody(String service, String serviceNamespace, Map params) {
{ ->
"$service"(xmlns: serviceNamespace) {
params.each { method, argument ->
delegate."$method"(argument)
}
}
}
}
def doIt(String action, String service, String serviceNamespace, Map params) {
def client = new Client()
client.send(action) {
body(getRequestBody(service, serviceNamespace, params))
}
}
I'm trying to create route that would wrap caught exception into my exception that carries user object of logged-in user. Ideally I prefer to put that information into ContextRequest in order to use it in my ExceptionHandler but it's immutable.
I concluded that the right way to do it is to use mapInnerRoute as described in this example.
def completeWithUserAwareException(user: User) =
mapInnerRoute { route =>
ctx =>
try {
route(ctx)
} catch {
case ex: Throwable =>
ctx.fail(new ExceptionWithUser(user, 0, ex))
}
}
which I use like this
val authenticatedRoutes = myAuthorization { user =>
completeWithUserAwareException(user) {
pathPrefix("admin") {
(get & path("plugins")) {
complete {
""
}
}
}
}
}
Content of completeWithUserAwareException is never invoked. I suspect it has something to do with dynamic nature of outer route.
How can I achieve my goal of passing user context information to exception handler ?
Try
def completeWithUserAwareException(user: User) =
handleExceptions(
ExceptionHandler {
case NonFatal(ex) => failWith(new ExceptionWithUser(user, 0, ex))
})
I've a route as follows:
val route = {
logRequestResult("user-service") {
pathPrefix("user") {
get {
respondWithHeader(RawHeader("Content-Type", "application/json")) {
parameters("firstName".?, "lastName".?).as(Name) { name =>
findUserByName(name) match {
case Left(users) => complete(users)
case Right(error) => complete(error)
}
}
}
} ~
(put & entity(as[User])) { user =>
complete(Created -> s"Hello ${user.firstName} ${user.lastName}")
} ~
(post & entity(as[User])) { user =>
complete(s"Hello ${user.firstName} ${user.lastName}")
} ~
(delete & path(Segment)) { userId =>
complete(s"Hello $userId")
}
}
}
}
The content type of my response should always be application/json as I've it set for the get request. However, what I'm getting in my tests is text/plain. How do I set the content type correctly in the response?
On a side note, the akka-http documentation is one of the most worthless piece of garbage I've ever seen. Almost every link to example code is broken and their explanations merely state the obvious. Javadoc has no code example and I couldn't find their codebase on Github so learning from their unit tests is also out of the question.
I found this one post that says "In spray/akka-http some headers are treated specially". Apparently, content type is one of those and hence can't be set as in my code above. One has to instead create an HttpEntity with the desired content type and response body. With that knowledge, when I changed the get directive as follows, it worked.
import akka.http.scaladsl.model.HttpEntity
import akka.http.scaladsl.model.MediaTypes.`application/json`
get {
parameters("firstName".?, "lastName".?).as(Name) { name =>
findUserByName(name) match {
case Left(users) => complete(users)
case Right(error) => complete(error._1, HttpEntity(`application/json`, error._2))
}
}
}
I want to create a route that matches only if the client sends a specific Accept header. I use Spray 1.2-20130822.
I'd like to get the route working:
def receive = runRoute {
get {
path("") {
accept("application/json") {
complete(...)
}
}
}
}
Here I found a spec using an accept() function, but I can't figure out what to import in my Spray-Handler to make it work as directive. Also, I did not find other doc on header directives but these stubs.
I would do this way:
def acceptOnly(mr: MediaRange*): Directive0 =
extract(_.request.headers).flatMap[HNil] {
case headers if headers.contains(Accept(mr)) ⇒ pass
case _ ⇒ reject(MalformedHeaderRejection("Accept", s"Only the following media types are supported: ${mr.mkString(", ")}"))
} & cancelAllRejections(ofType[MalformedHeaderRejection])
Then just wrap your root:
path("") {
get {
acceptOnly(`application/json`) {
session { creds ⇒
complete(html.page(creds))
}
}
}
}
And by the way the latest spray 1.2 nightly is 1.2-20130928 if you can, update it
There is no pre-defined directive called accept directive. You can see the full list of available directives here.
However, you can use the headerValueByName directive to make a custom directive that does what you desire:
def accept(required: String) = headerValueByName("Accept").flatMap {
case actual if actual.split(",").contains(required) => pass
case _ => reject(MalformedHeaderRejection("Accept", "Accept must be equal to " + required))
}
Put this code in scope of your spray Route, then just use as you have shown in your question.