Write a simple json REST server using spray in scala
Asked Answered
I

3

21

I want to implement a simple json REST server using spray in scala that supports the following routes:

GET /foo => return a list of case class objects in json format
POST /bar => read a json into a case class object and perform some computation

My basic starter code is the following:

import spray.routing.SimpleRoutingApp
import spray.can.Http
import akka.actor.ActorSystem
import akka.actor.Props
import akka.io.IO
import scala.collection.JavaConversions
import com.fasterxml.jackson.databind.ObjectMapper

object SprayTest extends App with SimpleRoutingApp {
  implicit val system = ActorSystem("my-system")
  val mapper = new ObjectMapper

  case class Foo(a: String, b: Int)
  case class Bar(c: Long, d: String)

  startServer(interface = "localhost", port = 8080) {
    get {
      path("foo") {
        complete {
          val c = listOfFoo()
          mapper.writeValueAsString(c)
        }
      }
    } ~ post {
      path("bar") {
        val bar: Bar = ???
        complete {
          "???"
        }
      }
    }
  }
}

The two most important open issues with this code that I know of are:

  1. I'm depending on jackson, but from searching the web, it seems like spray should have some sort of built in support for serializing and deserializing simple case objects or lists of case objects.

  2. I'm not sure the "best", most idiomatic and succinct way to get content from the post request and marshall it into json so that I can perform some computations on the case class object(s)

Does anyone know the best approach? Is there a way to make the marshalling automatic, so I can execute something like complete { caseObject } and have caseObject convert automatically into json (and vice versa with defining POST method)?

Inure answered 6/6, 2014 at 23:29 Comment(0)
P
20

Definitely use spray json. Usually you separate the data models into their own file:

import spray.json._

case class Foo(a: String, b: Int)
case class Bar(c: Long, d: String)

object FooBarJsonProtocol extends DefaultJsonProtocol{
    implicit val fooFormat = jsonFormat2(Foo)
    implicit val barFormat = jsonFormat2(Bar)
}

Then in the route

    import FooBarJsonProtocol._
    ...
    get {
      path("foo") {
        complete {
          listOfFoo() //with the implicit in scope this will serialize as json
        }
      }
    } ~ post {
      path("bar") {
        entity(as[Bar]) { bar =>  //extract json Bar from post body
          complete(bar) //serialize bar to json (or whatever processing you want to do)
        }
      }
    }
  }
Pecos answered 7/6, 2014 at 3:24 Comment(2)
In spray-json 1.3.3 important to import import spray.httpx.SprayJsonSupport._Towroy
Beware of ordering/import too.Alexio
O
7

I can't imagine why this question was down-voted -- it seems specific and well-expressed.

It is a bit hard to find, but the Spray docs cover case class extraction under Spray Routing / Advanced Topics. It would be pointless to repeat their explanation here, but basically you want to use as[Foo] to deserialize HTTP content into objects. The entity directive can be used to do this for the body of a request, as shown in this longer example of the spray-routing DSL. mapTo (used in the same example) is probably what you want to serialize an object for the response.

For JSON, it would probably be easiest to use their separate Spray-JSON library, since it plugs right into their typeclass mechanism, but I think you could with some effort marry up anything you want. Spray-JSON can handle a case class with one line of glue.

By the way, the line val bar: Bar = ??? in your sample code is going to be executed at the time the route is defined, not when a request comes in, as you presumably want. Please read the Understanding the DSL Structure section of the spray-routing docs.

Osei answered 7/6, 2014 at 2:37 Comment(0)
S
2

Got it thanks, here is my answer

import spray.routing._
import spray.json._
import spray.httpx._
import spray.http._


case class UserLogin(username: String, password: String)

object UserLoginJsonSupport extends DefaultJsonProtocol with SprayJsonSupport {
   implicit val PortofolioFormats = jsonFormat2(UserLogin)
}

import UserLoginJsonSupport._


trait UserAccountsServiceAPI extends HttpService{       
    val UserAccountsServiceRouting = {
      path("api" / "userservice" ) {   
        post {
           entity(as[UserLogin]) { userLogin =>
                println("->"+userLogin.username)
                println("->"+userLogin.password)
                complete(userLogin.username)
           }
        }
      }
   }
}

My http request with curl app

curl -i 'http://localhost:8080/api/userservice' -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "pwd"}'
Sianna answered 20/10, 2015 at 21:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.