How to embed Play 2.6 using Akka HTTP Server? - scala

Play 2.5 used Netty as default and allowed easy embedding. https://www.playframework.com/documentation/2.5.x/ScalaEmbeddingPlay
How is this now done with Akka HTTP Server, which is now the default server backend?
The page https://www.playframework.com/documentation/2.6.x/ScalaEmbeddingPlay is missing.

The documentation was split in two pages:
For Akka HTTP Server: https://www.playframework.com/documentation/2.6.1/ScalaEmbeddingPlayAkkaHttp
For Netty Server: https://www.playframework.com/documentation/2.6.1/ScalaEmbeddingPlayNetty
You should use AkkaHttpServerComponents to avoid deprecated APIs:
import play.api.{ BuiltInComponents, NoHttpFiltersComponents }
import play.api.mvc._
import play.api.routing.Router
import play.api.routing.sird._
import play.core.server.AkkaHttpServerComponents
val server = new AkkaHttpServerComponents with BuiltInComponents with NoHttpFiltersComponents {
// To avoid using the deprecated Action builder while
// keeping the `Action` idiom.
private val Action = defaultActionBuilder
override def router: Router = Router.from {
case GET(p"") => Action {
Results.Ok("Hello, World")
}
}
}.server

Related

Kafka Producer: how code onSuccess/onError callbacks with Reactive and Non-Blocking Producer

I am trying following Micronaut Kafka guide. It shows this piece of code:
Single<Book> sendBook(
#KafkaKey String author,
Single<Book> book
);
I tried implement whithout success that way
Producer
package com.tolearn.producer
import io.micronaut.configuration.kafka.annotation.KafkaClient
import io.micronaut.configuration.kafka.annotation.KafkaKey
import io.micronaut.configuration.kafka.annotation.Topic
import io.micronaut.messaging.annotation.Header
import io.reactivex.Single
#KafkaClient(
id = "demo-producer",
acks = KafkaClient.Acknowledge.ALL)
#Header(name = "X-Token", value = "\${my.application.token}")
public interface DemoProducer {
//Reactive and Non-Blocking
#Topic("demotopic")
fun sendDemoMsgReactive(
#KafkaKey key: String?,
msg: Single<String>): Single<String?>?
}
And call it from service layer by
package com.tolearn.service
import com.tolearn.producer.DemoProducer
import io.reactivex.Single
import io.reactivex.SingleOnSubscribe
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
#Singleton
class DemoService {
#Inject
#Named("dp")
lateinit var dp : DemoProducer
fun postDemo(key: String, msg: String){
//Reactive and No-blocking
val singleReturned:Single<String> = dp.sendDemoMsgReactive(key, SingleOnSubscribe<String> msg ).subscribe()
singleReturned.doOnSuccess{
print("ok")
}
singleReturned.doOnError ((e)->print(e))
}
}
Basically, what I want is post a message to kafka "no-blocking" style using io.reactivex.Single. I understand I must subscribe and then code two callbacks:onSuccess and onError. Certainly I am missing some basic concepts regard ReactiveX. Kindly, any clue will be appreciatted.

Quarkus could not find writer for content-type multipart/form-data rest client

I'm implementing an API-gateway for my rest microservice using Quarkus.
I want to forward requests to another (Quarkus) rest-api.
I'm trying to forward a POST request with multiform data.
I'm expecting to get a 201 but I'm getting a 500 internal server error.
RESTEASY throws the following error:
RESTEASY002020: Unhandled asynchronous exception, sending back 500: javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: javax.ws.rs.ProcessingException: RESTEASY003215: could not find writer for content-type multipart/form-data type: org.acme.rest.client.multipart.MultipartBody
I've tried upgrading my Quarkus version from 1.4.2 to 1.5.2 because I saw the following issue:
https://github.com/quarkusio/quarkus/issues/8223
Also tried Intellij invalidate cache/restart, re-import maven
Code
MultiPartBody:
package org.acme.rest.client.multipart;
import org.jboss.resteasy.annotations.providers.multipart.PartType;
import javax.ws.rs.FormParam;
import javax.ws.rs.core.MediaType;
public class MultipartBody {
#FormParam("sample_id")
#PartType(MediaType.TEXT_PLAIN)
public Long sampleId;
#FormParam("username")
#PartType(MediaType.TEXT_PLAIN)
public String username;
#FormParam("content")
#PartType(MediaType.TEXT_PLAIN)
public String content;
}
Interface:
package org.acme.rest.client;
import io.smallrye.mutiny.Uni;
import org.acme.rest.client.multipart.MultipartBody;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#RegisterRestClient
#Consumes(MediaType.MULTIPART_FORM_DATA)
public interface SocialService {
#POST
Uni<Response> create(#MultipartForm MultipartBody data);
}
Resource:
package org.acme.rest.client;
import io.smallrye.mutiny.Uni;
import org.acme.rest.client.multipart.MultipartBody;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#Path("/comments")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public class SocialResource {
#Inject
#RestClient
SocialService socialService;
#POST
public Uni<Response> create(#MultipartForm MultipartBody data) {
return socialService.create(data);
}
}
Test:
package org.acme.rest.client;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
#QuarkusTest
public class SocialResourceTest {
#Test
public void create(){
given().contentType("multipart/form-data")
.multiPart("sample_id", "1")
.multiPart("username", "testuser")
.multiPart("content", "test message")
.when()
.post("/comments")
.then()
.statusCode(201);
}
}
To fix this issue with Resteasy and quarkus, I had to add the MultipartFormDataWriter to resteasy client registry, this is my new code :
Any question click connect to my twitter #AIDeveloper
MultipartFormDataOutput output = new MultipartFormDataOutput();
output.addFormData("file", fileInputStream, MediaType.APPLICATION_OCTET_STREAM_TYPE);
output.addFormData("status", "status1", MediaType.TEXT_PLAIN_TYPE);
Response uploadResponse = newClient()
.target(uploadUri)
.register(MultipartFormDataWriter.class)
.request()
.post(Entity.entity(output
, MediaType.MULTIPART_FORM_DATA_TYPE));
Everything looks fine, but maybe you are missing this dependency
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
</dependency>
That info is available at this quarkus guide
I've had the same issue (could not find writer for content-type multipart/form-data). I solved it by making the MultiPartBody extend MultipartFormDataOutput.
In your case this would be:
...
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
...
public class MultipartBody extends MultipartFormDataOutput {
...
}
I found this solution by looking at how Quarkus / Resteasy internally resolves the output writers. This is done in the resolveMessageBodyWriter() method of org.jboss.resteasy.core.providerfactory.ResteasyProviderFactoryImpl. The relevant writer is org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataWriter. There, the isWriteable() method checks if MultipartFormDataOutput is a superclass of your class (MultipartBody).
However, I don't know why it works without extending MultipartFormDataOutput in the Quarkus examples.
My Quarkus version is 1.8.2.Final and I use the io.quarkus.quarkus-resteasy-multipart maven dependency, not org.jboss.resteasy.resteasy-multipart-provider.
The ProcessingException with message RESTEASY003215: could not find writer for content indicates no serializer was bound for the request type. This usually means you're missing the rest client dependency needed for the type.
In your case that might be today (as of Quarkus 2.5.0)
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-multipart</artifactId>
</dependency>
Another possibility for this very same error is you're trying to implement a REST client with the reactive API and missing the reactive client API bindings. You can have both reactive and regular rest-client at the same time in your project.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>
It might seems unrelated because it brings the Json writers in that case, but the exception and error message you'll get is exactly the same.

Play Framework: value withPrefix is not a member of api.ApiRouter

I try to create sird Router in Play Framework (from official tutorial https://www.playframework.com/documentation/2.6.x/ScalaSirdRouter#Binding-sird-Router).
I created the same structure and files but I get error after compile "Compilation error value withPrefix is not a member of api.ApiRouter".
ApiRouter (from official tutorial):
package api
import javax.inject.{Inject, Singleton}
import play.api.routing.Router.Routes
import play.api.routing.SimpleRouter
import play.api.routing.sird._
class ApiRouter #Inject()(controller: controllers.ApiController) extends SimpleRouter {
override def routes: Routes = {
case POST(p"/") => controller.message("test")
}
}
ApiController works, routes file has error "cannot resolve api.ApiRouter" in
-> /api api.ApiRouter
I tried to change path and namespace in ApiRoter file (file put in app/controllers, namespace change to "controllers" and change to "controllers.ApiController" in routes line) but the same error.
Where did I mistake?

Embedding multiple tests inside an akka-http route check

I'm using akka-http for the first time - my usual web framework of choice is http4s - and I'm having trouble getting the way I usually write endpoint unit tests to work with the route testing provided by akka-http-testkit.
Generally, I use ScalaTest (FreeSpec flavour) in order to set up an endpoint call and then run several separate tests on the response. For akka-http-testkit, this would look like:
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.scalatest.{FreeSpec, Matchers}
final class Test extends FreeSpec with ScalatestRouteTest with Matchers {
val route: Route = path("hello") {
get {
complete("world")
}
}
"A GET request to the hello endpoint" - {
Get("/hello") ~> route ~> check {
"should return status 200" in {
status should be(StatusCodes.OK)
}
"should return a response body of 'world'" in {
responseAs[String] should be("world")
}
//more tests go here
}
}
}
This errors with
java.lang.RuntimeException: This value is only available inside of a `check` construct!
The problem is the nested tests inside the check block - for some reason, values like status and responseAs are only available top-level within that block. I can avoid the error by saving the values I'm interested in to local variables top-level, but that's awkward and capable of crashing the test framework if e.g. the response parsing fails.
Is there a way around this, without putting all my assertions into a single test or performing a new request for each one?
You can group your test like that
"A GET request to the hello endpoint should" in {
Get("/hello") ~> route ~> check {
status should be(StatusCodes.OK)
responseAs[String] should be("world")
//more tests go here
}
}

Why does my Lift Comet Actor stops working after deployment on Jetty?

I'm playing around with Liftweb and its Comet support and I wrote some code which works fine on my local setup/computer. But as soon as I deploy the webapp to a production system (also Jetty), the CometActor is not doing anything. What is wrong with my code?
package code
package comet
import net.liftweb._
import http._
import net.liftweb.common.{Box, Full}
import net.liftweb.util._
import net.liftweb.actor._
import scala.language.postfixOps
import net.liftweb.util.Helpers._
import net.liftweb.http.js.JsCmds.{SetHtml}
import net.liftweb.http.js.jquery.JqJsCmds.{PrependHtml}
import net.liftweb.http.js.JE.{Str}
import _root_.scala.xml.{Text, NodeSeq}
import org.apache.commons.io.input._
case class LogLine(str: String)
class MyTailerListener(logActor: LiftActor) extends TailerListenerAdapter {
override def handle(line: String) {
logActor ! LogLine(line)
}
}
class CometLogEntry extends CometActor {
val listener = new MyTailerListener(this)
val tailer = Tailer.create(new java.io.File("/var/log/syslog"), listener)
override def defaultPrefix = Full("log_entry")
def render = bind("newest" -> <span id="newest">No log enties yet!</span>)
// Schedule an update every 5 seconds
Schedule.schedule(this, LogLine, 5 seconds)
override def lowPriority = {
case LogLine(str:String) => {
// Prepend the newest log line
partialUpdate(PrependHtml("newest", <li style="list-style-type: none;">{str}</li>))
Schedule.schedule(this, LogLine, 5 seconds)
}
}
}
I'm not sure if this will solve your problem, but your implementation of render could potentially cause issues. Render may be called at any time, not just when the CometActor is created. It should result in an up-to-date view of the component. In this case I'd either store the line you start your tail from so you can re-send the lines from that point or make your render function a NOOP.
Also, please use CSS Selectors instead of bind calls.
This line was causing the problem:
val tailer = Tailer.create(new java.io.File("/var/log/syslog"), listener)
On the server Jetty runs as user jetty which has no sufficient rights to read the file. Strange that I didn't get any "Permission denied" message or something. So, if I run Jetty as root my CometActor works well.
But running Jetty as root is considered dangerous. Here is a better solution:
sudo apt-get install acl
sudo setfacl -m u:jetty:r-x,g:adm:r-x /var/log/
sudo setfacl -m u:jetty:r--,g:adm:r-- /var/log/syslog