Hey i am working on scala play framework, what i need to do is, when someone enter some random(unknown) url, that is not defined in routes file, they need to be routed to the some another not found page, with 404 page not found http header response. instead of displaying the whole routes file as error.
using sbt : sbt launcher version 0.13.8
using scala : scalaVersion := "2.11.6"
using play framework 2x
You can override these in your Global.scala / Global.java file. For Java the could look similar to this:
public F.Promise<Result> onHandlerNotFound(Http.RequestHeader request) {
return F.Promise.<Result>pure(notFound(
Messages.get("error.routeNotFound")
));
}
I guess you are looking for Scala code for Global.scala to display your own 404 Page.
Global.scala should be under your /views folder. And override the onHandlerNotFound method in it, like this:
override def onHandlerNotFound(request: RequestHeader) = {
var cookies: Seq[Cookie] = Seq()
Future.successful(NotFound("hello world!!!!").withCookies(cookies:_*))
}
The NotFound is a PageCtrl.Status method which may need a import at the top of Global.scala. You can try using NotFound("Hello World") to see what's happening. It will display pure text "hello world" instead of default 404 page.
So the final code should be like
override def onHandlerNotFound(request: RequestHeader) = {
var cookies: Seq[Cookie] = Seq()
Future.successful(NotFound(view.html.yourOwn404PageHtml).withCookies(cookies:_*))
}
The withCookies is just used to create a Result object. You can use other method belongs to NotFound().
In Play2.6 you can handle unknown/broken API call buy using below code.
import javax.inject._
import models.Response
import play.api._
import play.api.http.DefaultHttpErrorHandler
import play.api.libs.json.Json
import play.api.mvc.Results._
import play.api.mvc._
import play.api.routing.Router
import _root_.controllers.JSONHelper.ResponseWrites
import scala.concurrent._
class ErrorHandler #Inject()(env: Environment,
config: Configuration,
sourceMapper: OptionalSourceMapper,
router: Provider[Router])
extends DefaultHttpErrorHandler(env, config, sourceMapper, router) {
override protected def onNotFound(request: RequestHeader, message: String): Future[Result] = {
Future.successful(
NotFound(Json.toJson(Response(isSuccess = false,"Resource Not Found", ("", ""))))
)
}
}
You can use customized ErrorHandler to return a json format error message, status result with status code or blank page instead.
package errors
import play.api.http.HttpErrorHandler
import play.api.mvc._
import play.api.mvc.Results._
import scala.concurrent._
import javax.inject.Singleton
#Singleton
class MyErrorHandler extends HttpErrorHandler {
def onClientError(request: RequestHeader, statusCode: Int, message: String) = {
Future.successful(
Ok() // Replace with your error handle here
)
}
def onServerError(request: RequestHeader, exception: Throwable) = ???
}
In application.conf
play.http.errorHandler = "errors.MyErrorHandler"
You can find detail on playframework document.
https://www.playframework.com/documentation/2.6.x/ScalaErrorHandling
Related
I need to return a json response in camelCase with finatra but it is in snake_case by default. From what I found so far, I need to use ObjectMapper, but I can't understand where do I pass it once I create it. An example would be very helpful. Here is what I have:
import com.twitter.finagle.http.Request
import com.twitter.finatra.http.Controller
class myTargetingController extends Controller {
val endpoint = "http://....."
get(s"$endpoint/?") { request: Request =>
// what do I do with it?
// val objectMapper = ScalaObjectMapper.builder.camelCaseObjectMapper
response.ok.json(myObject)
}
}
==================================================================
import com.twitter.finagle.{Service, SimpleFilter}
import com.twitter.finagle.http.{ Request, Response}
import com.twitter.finatra.http.routing.HttpRouter
import com.twitter.finatra.http.{HttpServer}
import com.twitter.finatra.http.filters.CommonFilters
import com.twitter.util.Future
object MyServerApp extends MyServer
class MyServer extends HttpServer {
override protected def configureHttp(router: HttpRouter) {
router
.filter[CommonFilters]
.add[CorsFilter, MyController]
}
}
P.S. I am very-very new to Scala
Following up from the comments
Define a custom ObjectMapperModule
class CamelCaseModule extends ScalaObjectMapperModule {
override val propertyNamingStrategy: PropertyNamingStrategy =
new PropertyNamingStrategy.UpperCamelCaseStrategy
override def additionalMapperConfiguration(mapper: ObjectMapper): Unit = {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
}
}
Override the default Jackson module for your server
override def jacksonModule = new CamelCaseModule
Make sure you have
"com.twitter" %% "finatra-jackson" % yourFinatraVersion % "test"
in your build.sbt
And that you import
import com.fasterxml.jackson.databind.{DeserializationFeature, Module, ObjectMapper, PropertyNamingStrategy}
import com.twitter.finatra.jackson.modules.ScalaObjectMapperModule
Tested it locally and it seems to work
Hope this helps
I have an existing Scala play application which has a REST API that calls another external REST API. I want to mock the external Web service returning fake JSON data for internal tests. Based on example from: https://www.playframework.com/documentation/2.6.x/ScalaTestingWebServiceClients
I followed example exactly as in Documentation and I'm getting compiler errors due to deprecated class Action.
import play.core.server.Server
import play.api.routing.sird._
import play.api.mvc._
import play.api.libs.json._
import play.api.test._
import scala.concurrent.Await
import scala.concurrent.duration._
import org.specs2.mutable.Specification
import product.services.market.common.GitHubClient
class GitHubClientSpec extends Specification {
import scala.concurrent.ExecutionContext.Implicits.global
"GitHubClient" should {
"get all repositories" in {
Server.withRouter() {
case GET(p"/repositories") => Action {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
} { implicit port =>
WsTestClient.withClient { client =>
val result = Await.result(
new GitHubClient(client, "").repositories(), 10.seconds)
result must_== Seq("octocat/Hello-World")
}
}
}
}
}
object Action in package mvc is deprecated: Inject an ActionBuilder
(e.g. DefaultActionBuilder) or extend
BaseController/AbstractController/InjectedController
And this is the primary example from latest official docs which in fact contains a compile time error, given this example doesn't work how should be the proper way to easily mock an external API using Scala Play?
You may change your example to:
Server.withRouterFromComponents() { cs => {
case GET(p"/repositories") => cs.defaultActionBuilder {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
}
} { implicit port =>
WsTestClient.withClient { client =>
val result = Await.result(
new GitHubClient(client, "").repositories(), 10.seconds)
result should be(Seq("octocat/Hello-World"))
}
}
To be honest, I'm not 100% sure if this is the nicest way. However I have submitted a PR to the play framework so you might watch that space for comments from the makers.
If you're using standalone version of play-ws you can use this library https://github.com/f100ded/play-fake-ws-standalone like this
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import org.f100ded.play.fakews._
import org.scalatest._
import play.api.libs.ws.JsonBodyWritables._
import scala.concurrent.duration.Duration
import scala.concurrent._
import scala.language.reflectiveCalls
/**
* Tests MyApi HTTP client implementation
*/
class MyApiClientSpec extends AsyncFlatSpec with BeforeAndAfterAll with Matchers {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
behavior of "MyApiClient"
it should "put access token to Authorization header" in {
val accessToken = "fake_access_token"
val ws = StandaloneFakeWSClient {
case request # GET(url"http://host/v1/foo/$id") =>
// this is here just to demonstrate how you can use URL extractor
id shouldBe "1"
// verify access token
request.headers should contain ("Authorization" -> Seq(s"Bearer $accessToken"))
Ok(FakeAnswers.foo)
}
val api = new MyApiClient(ws, baseUrl = "http://host/", accessToken = accessToken)
api.getFoo(1).map(_ => succeed)
}
// ... more tests
override def afterAll(): Unit = {
Await.result(system.terminate(), Duration.Inf)
}
}
Using Scala play version 2.5 and trying to follow the guidelines for unit testing controllers as per documentation at: https://www.playframework.com/documentation/2.5.x/ScalaTestingWithScalaTest
there's no example for unit testing an async controller.
I'm trying to create a unit test for my controller which has an async action method, I ended up mocking some objects
class ProductController #Inject()(
action: ProductAction,
handler: ProductResourceHandler)(implicit ec: ExecutionContext)
extends Controller {
/**
* Fetch a list of products
*/
def index: Action[AnyContent] = {
action.async { implicit request =>
handler.find.map { list =>
Ok(Json.toJson(list))
}
}
}
// ...
}
My unit test:
import scala.concurrent.Future
import org.scalatestplus.play._
import play.api.mvc._
import play.api.test._
import play.api.test.Helpers._
import org.scalatest.mockito.MockitoSugar
import product.ProductAction
import product.ProductController
import product.services.maps.GeolocationService
import product.ProductResourceHandler
import play.api.libs.concurrent.Execution.Implicits._
import scala.io.Source
import play.api.libs.json.Json
import product.model.OfferList
import product.model.OfferDetail
import org.mockito.Mockito._
class ProductControllerSpec extends PlaySpec with Results with MockitoSugar {
private val productList = Json.parse(Source.fromFile("conf/app/sample_products.json").getLines.mkString).as[ProductList]
"Example Page#index" should {
"should be valid" in {
val action = new ProductAction()
val handler = mock[ProductResourceHandler]
when(handler.find) thenReturn Future.successful(productList)
val controller = new ProductController(action, handler)
val result: Future[Result] = controller.index().apply(FakeRequest())
val bodyText: String = contentAsString(result)
bodyText != null mustBe true
}
}
}
up till now it's working but I'm wondering if this follows the best practices or guidelines for this type of test. Is this the right way to unit test an async controller in Scala play framework?
Some of my suggestions are
use contentAsJson instead of contentAsString and inspect the returned json.
use route to directly invoke the controller and test response (for eg route(app, FakeRequest..)
use status method to check if the returned status is HTTP OK (status code 200)
val Some(result) = route(app, FakeRequest(GET,
controllers.routes. ProductController.index.path()))
status(result) must be (OK)
val json = contentAsJson(result)
// inspect json fields like if you have to check if the json
// has string field called id you can do (json \ "id").as[String] must be ("<id value>")
According to Play documentation:
Play actions are asynchronous by default.
This means even if you are not using Action.async { Future { myAnonymousFunction } } but just Action { myAnonymousFunction }, internally the result of myAnonymousFunction will be enclosed in a Future.
For instance, say you have
class HelloWorld extends Controller {
def index = Action { request => Ok("") }
}
then
(new HelloWorld).index().apply(FakeRequest())
still has type
Future[Result]
This leads me to believe that your unit test is indeed appropriate way of testing controllers, that is, Play's documentation is implicitly covering also the case of Action.async.
I'm trying to write a little REST api using Scala, Spray.io, Elastic4s and ElasticSearch.
My ES instance is running with default parameters, I just changed the parameter network.host to 127.0.0.1.
Here is my spray routing definition
package com.example
import akka.actor.Actor
import spray.routing._
import com.example.core.control.CrudController
class ServiceActor extends Actor with Service {
def actorRefFactory = context
def receive = runRoute(routes)
}
trait Service extends HttpService {
val crudController = new CrudController()
val routes = {
path("ads" / IntNumber) {
id =>
get {
ctx =>
ctx.complete(
crudController.getFromElasticSearch
)
}
}
}
}
My crudController :
package com.example.core.control
import com.example._
import org.elasticsearch.action.search.SearchResponse
import scala.concurrent._
import scala.util.{Success, Failure}
import ExecutionContext.Implicits.global
class CrudController extends elastic4s
{
def getFromElasticSearch : String = {
val something: Future[SearchResponse] = get
something onComplete {
case Success(p) => println(p)
case Failure(t) => println("An error has occured: " + t)
}
"GET received \n"
}
}
And a trait elastic4s who is encapsulating the call to elastic4s
package com.example
import com.sksamuel.elastic4s.ElasticClient
import com.sksamuel.elastic4s.ElasticDsl._
import scala.concurrent._
import org.elasticsearch.action.search.SearchResponse
trait elastic4s {
def get: Future[SearchResponse] = {
val client = ElasticClient.remote("127.0.0.1", 9300)
client execute { search in "ads"->"categories" }
}
}
This code runs well, and gives me this output :
[INFO] [03/26/2014 11:41:50.957] [on-spray-can-akka.actor.default-dispatcher-4] [akka://on-spray-can/user/IO-HTTP/listener-0] Bound to localhost/127.0.0.1:8080
But when a try to access to the route "localhost/ads/8" with my browser, the case Failure is always triggered and I got this error output on my intellij console :
An error has occured: org.elasticsearch.transport.RemoteTransportException: [Skinhead][inet[/127.0.0.1:9300]][search]
(No console output with elasticSearch running on my terminal)
Is this exception related to ElasticSearch, or am I doing wrong with my Future declaration ?
I suppose you should use ElasticClient.local in this case, as specified in elastic4s docs:
https://github.com/sksamuel/elastic4s
To specify settings for the local node you can pass in a settings object like this:
val settings = ImmutableSettings.settingsBuilder()
.put("http.enabled", false)
.put("path.home", "/var/elastic/")
val client = ElasticClient.local(settings.build)
Is it possible to add a cookie in the Play Framework's doFilter method in the Global class?
I've tried:
override def doFilter(action: EssentialAction): EssentialAction = EssentialAction { request =>
if (request.queryString.contains("q")) {
action.apply(request).map(_.withCookies(
Cookie("q", request.queryString.get("q").get(0), 3600)
))
}
}
but the cookie doesn't get sent to the browser.
I'm using Playframework 2.2 and use a similar thing:
import play.api.mvc._
import play.api._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object ResponseFilter extends Filter {
def apply(next: (RequestHeader) => Future[SimpleResult])(rh: RequestHeader) = {
next(rh).map(_.withHeaders("Access-Control-Allow-Origin" -> "*").as("application/json; charset=utf-8"))
}
}
And my Global object looks like this:
import play.api._
import play.api.mvc._
import service.ResponseFilter
object Global extends WithFilters(ResponseFilter) with GlobalSettings
This works fine for me. So I suppose you could replace the _.withHeaders(...) part with the _.withCookies(...) part and that would work for you as well.
Please note that things are a bit different in earlier versions of playframework