Akka gRPC/HTTP Interop giving 404 - scala

I have an application running a gRPC service alongside a simple Akka HTTP endpoint. I am following this guide: https://doc.akka.io/docs/akka-grpc/current/server/akka-http.html. The problem: when curling the HTTP endpoint I get 404 not found. I know it found the server because Akka-HTTP/10.2.5 is the server of the response header.
Some code:
object Server extends App {
val conf = ConfigFactory
.parseString("akka.http.server.preview.enable-http2 = on")
.withFallback(ConfigFactory.defaultApplication())
val system = ActorSystem("Interop", conf)
new Server(system).run()
}
class Server(system: ActorSystem) {
def run() = {
// implicit sys, ec...
val grpcService: HttpRequest => Future[HttpResponse] = ServiceHandler(new Service())
val greeter = get {
pathEndOrSingleSlash {
complete("I am alive")
}
}
// lifted this line straight out of the guide
val grpcRoute = { ctx => grpcService(ctx.request).map(RouteResult.Complete) }
val route = concat(greeter, grpcRoute)
val binding = Http().newServerAt("127.0.0.1", 8080).bind(route)
binding
}
}
When I take out the gRPC route, the greeter endpoint works as intended. Otherwise, when I curl http://localhost:8080, I get
*Mark bundle as not supporting multiuser
<HTTP/1.1 404 Not Found
<Server: akka-http/10.2.5
<other-stuff
I am using akka-gRPC 2.0.0
What should I do to ensure interop between the two routes?

gRPC uses HTTP/2 so try
curl --http2 http://localhost:8080
It should also get rid of the initial "Mark bundle..." message.

Related

Scala Jetty webapp on Heroku 404

I'm testing around with a Scala web framework (Udash) and trying to run a toy-example in Heroku. I have it running without issues in local following the instructions in the Heroku docs:
sbt compile stage
heroku local web
However, once deployed, any URL I type goes to 404, even the landing page of the app. These are the objects I am using:
object Launcher extends CrossLogging {
def main(args: Array[String]): Unit = {
val port = Properties.envOrElse("PORT", "5000").toInt
val server = new ApplicationServer(port, "frontend/target/UdashStatics/WebContent")
server.start()
logger.info(s"Application started...")
}
}
class ApplicationServer(val port: Int, resourceBase: String) {
private val server = new Server(port)
private val contextHandler = new ServletContextHandler
private val appHolder = createAppHolder()
contextHandler.setSessionHandler(new SessionHandler)
contextHandler.setGzipHandler(new GzipHandler)
contextHandler.getSessionHandler.addEventListener(new org.atmosphere.cpr.SessionSupport())
contextHandler.addServlet(appHolder, "/*")
server.setHandler(contextHandler)
def start(): Unit = server.start()
def stop(): Unit = server.stop()
private def createAppHolder() = {
val appHolder = new ServletHolder(new DefaultServlet)
appHolder.setAsyncSupported(true)
appHolder.setInitParameter("resourceBase", resourceBase)
appHolder
}
}
Is there any Heroku configuration/characteristic that I am missing?
EDIT
Tried to apply the changes suggested and ended up with the following ApplicationContext:
class ApplicationServer(val port: Int, val resourceBase: String) {
val server = new Server()
val connector = new ServerConnector(server)
connector.setPort(port)
server.addConnector(connector)
private val appHolder = createAppHolder()
val context = new ServletContextHandler(ServletContextHandler.SESSIONS)
context.setBaseResource(Resource.newResource(resourceBase))
context.setContextPath("/")
context.addServlet(appHolder, "/")
server.setHandler(context)
private def createAppHolder() = {
val appHolder = new ServletHolder("default", classOf[DefaultServlet])
appHolder.setInitParameter("dirAllowed", "true")
appHolder.setInitParameter("resourceBase", resourceBase)
appHolder
}
def start(): Unit = server.start()
def stop(): Unit = server.stop()
}
However, I still get Error 404 even on landing page after deploying to Heroku:
HTTP ERROR 404
Problem accessing /. Reason:
Not Found
When running the app on local I get to the landing page correctly.
Thank you!
Thanks!
A few things to adjust that might help you.
resourceBase as a init-parameter on DefaultServlet is for alternate static file serving.
Use ServletContextHandler.setBaseResource(Resource) instead.
Use Resource.newResource(String) to create a new Resource reference. This should be an absolute path on the filesystem, or an absolute URI reference. no relative paths or URI fragments.
The DefaultServlet must be on the url-pattern of "/", not "/*" (this is a servlet spec requirement)
The DefaultServlet must be named, and must have the name "default" (this is a servlet spec requirement, see link on point 1 for example)
set the ServletContextHandler.setContextPath("/") to be explicit about what base context-path you want to use.
Some observations:
Your example code will only serve static content out of the resourceBase.
Since you have no welcomeFiles configured it would serve <resourceBase>/index.html by default (if you don't specify a specific static resource you want to access)
You have a SessionListener setup (org.atmosphere.cpr.SessionSupport), but since there's nothing that would access a Session, that code is pretty much a no-op.
There's no dynamic results from a custom Servlet or Filter present in your example codebase.

Testing an Akka Stream containing a Akka Http cachedHostConnectionPool flow

Any ideas on how to best test an Akka Stream containing an Akka Http Flow? I'm struggling with the following method in particular:
def akkaHttpFlow(server: String)(implicit actorSystem: ActorSystem, actorMaterializer: ActorMaterializer) = {
val uri = new java.net.URI(server)
val port: Int = if( uri.getPort != -1) { uri.getPort } else { 80 }
Http().cachedHostConnectionPool[Seq[String]](uri.getHost, port)
.withAttributes(ActorAttributes.supervisionStrategy(decider))
}
This is the test code
val emails = Set("tonymurphy#example.com")
val source: Source[String, NotUsed] = Source(emails)
val f = source
.grouped(10)
.via(requestBuilderFlow)
.via(akkaHttpFlow)
.map(responseHandler)
.runForeach(println)
f.futureValue.shouldBe(Done)
It fails with the following error (not unexpected tbh) >>>
The future returned an exception of type: akka.stream.StreamTcpException, with message: Tcp command [Connect(localhost:9001,None,List(),Some(10 seconds),true)] failed because of Connection refused.
Would it be possible to embed akka http server in the test? Or how best could I structure the code to be able to mock it?
The supporting code
object MyOperations extends StrictLogging {
val requestBuilderFunc : Seq[String] => (HttpRequest, Seq[String]) = { emails : Seq[String] =>
HttpRequest(method = HttpMethods.POST, uri = "/subscribers").withEntity(ContentTypes.`application/json`, ByteString(Json.toJson(emails).toString())) -> emails.toVector
}
val requestBuilderFlow : Flow[Seq[String],(HttpRequest, Seq[String]),NotUsed] = Flow[Seq[String]] map requestBuilderFunc
val responseHandler: ((Try[HttpResponse], Seq[String])) => (HttpResponse, Seq[String]) = {
case (responseTry, context) =>
logger.debug(s"Response: $responseTry")
(responseTry.get, context.asInstanceOf[Seq[String]])
}
}
I have to admit I'm struggling with how to organise my scala applications into objects, traits, classes, higher order functions etc and test them
What you'll want to do is use something like dependency injection to inject a Flow[(HttpRequest, Seq[String]), (Try[HttpResponse], Seq[String]), Any].
In production that flow will be from akka http, but in test you can mock it yourself to return whatever you need.

java.io.IOException: WebSocket method must be a GET

I am trying to write a websocket client application where I got to subscribe for an websocket URL i am using play-ws for the same. But getting the exception like below.
Exception in thread "main" java.io.IOException: WebSocket method must
be a GET
Dependency used:
"com.typesafe.play" %% "play-ws" % "2.4.0-M1"
Piece of code I used to get the websocket client is below,
trait PlayHelper {
val config = new NingAsyncHttpClientConfigBuilder(DefaultWSClientConfig()).build()
val builder = new AsyncHttpClientConfig.Builder(config)
val wsClient = new NingWSClient(builder.build())
def getBody(future: Future[WSResponse]) = {
val response = Await.result(future, Duration.Inf);
if (response.status != 200)
throw new Exception(response.statusText);
response.body
}
}
object Client extends PlayHelper with App{
def subscribe()={
val url = "ws://localhost:8080"
val body = getBody(wsClient.url(url).get())
Thread.sleep(1000)
println(s"body: $body")
}
subscribe()
}
Exception screen shot is below:
Looking for the help for this issue.
I don't think that play-ws supports websockets, you may want to use the AsyncHttpClient directly: https://github.com/AsyncHttpClient/async-http-client#websocket

Echo simple HTTP server with Akka Http in Scala

I am developing a simple HTTP server using Akka-Http in Scala.
My code is as given below:
object HttpServer extends App {
override def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val route : Route = post {
path("echo") {
val json = ???
complete((StatusCodes.OK, json))
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine()
bindingFuture.flatMap(_.unbind())
port.onComplete(_ => system.terminate())
}
}
I do not know Scala enough yet. For that, I need some help.
I do not know how I can get JSON from Http POST body to give back that json to client.
You only need to add an extractor to your route definition:
val route : Route = post {
path("echo") {
entity(as[String]) { json =>
complete(json)
}
}
Note that you don't need to set the status code explicitly, as akka-http will automatically set status 200 OK for you when you pass a value to complete

Forwarding HTTP/REST Request to another REST server in Spray

I've a bunch of existing REST services (#1 and #2 below) that are running on different endpoints that are used internally only. Now I want to expose some of these REST APIs (API-1 and API-2) externally using Spray because this external endpoint will also provide some additional APIs (API-3, API-4).
Is there a simple/recommended way to forward the external REST requests to my new endpoint to existing REST endpoints?
It sounds like what you want is the proposed proxyTo directive:
path("foo") {
get {
proxyTo("http://oldapi.example.com")
}
}
(or, more likely, proxyToUnmatchedPath). There is an issue open for it:
https://github.com/spray/spray/issues/145
Looks like somebody has been working on this; here is a commit in a Spray fork:
https://github.com/bthuillier/spray/commit/d31fc1b5e1415e1b908fe7d1f01f364a727e2593
But the commit appears not yet to be in the master Spray repo. You could ask about its status on the issue page.
Also, here is a blog post from CakeSolutions about how you can do the proxying manually:
http://www.cakesolutions.net/teamblogs/http-proxy-with-spray
A comment on that page points out that Spray has an undocumented thing called ProxySettings, and points to the following tests for it:
https://github.com/spray/spray/blob/master/spray-can-tests/src/test/scala/spray/can/client/ProxySpec.scala
UPDATE; Soumya has asked the Spray team about this on the spray-user Google Group:
https://groups.google.com/forum/#!topic/spray-user/MlUn-y4X8RE
I was able to proxy a single service with the help of the CakeSolution blog. In the following example, the proxy is running on http://localhost:20000 and the actual REST endpoint is running at http://localhost:7001.
Not sure how proxy multiple services using this approach.
I like #cmbaxter's solution of using Nginx as the proxy but I'm still curious if there is a way to extend the following approach to do it in Spray.
import akka.actor.{ActorRef, Props}
import akka.io.IO
import akka.util.Timeout
import spray.can.Http
import spray.can.Http.ClientConnectionType
import spray.http.HttpResponse
import spray.routing.{RequestContext, HttpServiceActor, Route}
import scala.concurrent.duration._
import akka.pattern.ask
object ProxyRESTService {
def main(args: Array[String]) {
//create an actor system
implicit val actorSystem = akka.actor.ActorSystem("proxy-actor-system")
implicit val timeout: Timeout = Timeout(5 seconds)
implicit val dis = actorSystem.dispatcher
//host on which proxy is running
val proxyHost = "localhost"
//port on which proxy is listening
val proxyPort = 20000
//host where REST service is running
val restServiceHost = "localhost"
//port where REST service is running
val restServicePort = 7001
val setup = Http.HostConnectorSetup(
proxyHost,
proxyPort,
connectionType = ClientConnectionType.Proxied(restServiceHost, restServicePort)
)
IO(Http)(actorSystem).ask(setup).map {
case Http.HostConnectorInfo(connector, _) =>
val service = actorSystem.actorOf(Props(new ProxyService(connector)))
IO(Http) ! Http.Bind(service, proxyHost, port = proxyPort)
}
}
}
.
class ProxyService(connector: ActorRef) extends HttpServiceActor {
implicit val timeout: Timeout = Timeout(5 seconds)
implicit def executionContext = actorRefFactory.dispatcher
val route: Route = (ctx: RequestContext) => ctx.complete(connector.ask(ctx.request).mapTo[HttpResponse])
def receive: Receive = runRoute(route)
}