5

I am trying to write an Akka HTTP microservice (akka version 2.4.11, Scala version 2.11.8, both latest versions at time of writing) which is aware of the client service's IP (i.e., remote address), and I cannot get this to work.

I can create and run a service which says 'Hello!' using a route like this:

    val routeHello: Route = path("SayHello") {
      get {
        entity(as[String]) {
          body => complete {
            HttpResponse(entity = HttpEntity("Hello!"))
          }
        }
      }
    }

I have constructed a similar route to the one above, which is extended so that it is aware of the client's IP address.

I noted that I need to edit the application.conf file and set 'remote-address-header = on' to enable the addition of a Remote-Address header holding the clients (remote) IP address. I have done this in case it is required.

Here is the route:

    val routeHelloIp: Route = path("SayHelloIp") {
      get {
        // extractClientIp appears to be working as a filter
        // instead of an extractor - why?
        extractClientIp {
          clientIp => {
            entity(as[String]) {
              body => complete {
                HttpResponse(entity = HttpEntity("Hello!"))
              }
            }
          }
        }
      }
    }

However when I run this route, I get a message 'The requested resource could not be found.'.

It looks like I have got the Akka-http DSL syntactic sugar wrong in the example above. I would be grateful if you could put me on the right path!

EDIT:

I have tried the following program in response to Ramon's helpful answer. Unfortunately it does not compile and I cannot see what I need to do to make it compile.

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.IncomingConnection
import java.net.InetSocketAddress
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
import akka.http.scaladsl.server.Directives._
import java.net.InetSocketAddress


object TestHttp {
  def main(args: Array[String]) {

    implicit val system = ActorSystem("my-system")
    implicit val materializer = ActorMaterializer()

    // allow connections from any IP
    val interface = "0.0.0.0"

    //from the question
    def createRoute(address: InetSocketAddress) = path("SayHelloIp") {
      get {
        extractRequestEntity { entity =>
          entity(as[String]) { body =>
         complete(entity = s"Hello ${address.getAddress().getHostAddress()}")
          }
        }
      }
    }

    Http().bind(interface).runWith(Sink foreach { conn =>
  val address = conn.remoteAddress
   conn.handleWithAsyncHandler(createRoute(address))
    })
  }
}

I have the following build.sbt to ensure that the latest version of Scala and akka-http are used:

import sbt.Keys._

name := "Find my IP"

version := "1.0"

scalaVersion := "2.11.8"

resolvers ++= Seq(
  "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
)

libraryDependencies ++= {
  Seq(
    "com.typesafe.akka" %% "akka-actor" % "2.4.11",
    "com.typesafe.akka" %% "akka-stream" % "2.4.11",
    "com.typesafe.akka" %% "akka-http-experimental" % "2.4.11",
    "com.typesafe.akka" %% "akka-http-core" % "2.4.11"
  )
}

I get the following compile-time errors:

[error] /Users/tilopa/temp/akka-test/src/main/scala/Test.scala:24: akka.http.scaladsl.model.RequestEntity does not take parameters
[error]           entity(as[String]) { body =>
[error]                 ^
[error] /Users/tilopa/temp/akka-test/src/main/scala/Test.scala:25: reassignment to val
[error]             complete(entity = s"Hello ${address.getAddress().getHostAddress()}")
[error]                             ^
[error] two errors found
[error] (compile:compileIncremental) Compilation failed
ScalaNewbie
  • 51
  • 1
  • 4

2 Answers2

7

Using extractClientIp

extractClientIp is not working for you because the sender has not specified one of the required header fields. From the documentation:

Provides the value of X-Forwarded-For, Remote-Address, or X-Real-IP headers as an instance of RemoteAddress.

You just have to turn on the right setting in your sender:

The akka-http server engine adds the Remote-Address header to every request automatically if the respective setting akka.http.server.remote-address-header is set to on. Per default it is set to off.

generic solution

If you want this to work for any HttpRequest, not just the ones with the correct header settings, then you have to use the bind method on an HttpExt instead of bindAndHandle:

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.IncomingConnection

import java.net.InetSocketAddress

implicit val actorSystem : ActorSystem = ???
implicit val actorMat = ActorMaterializer()


//alow connections from any IP
val interface = "0.0.0.0"

//from the question
def createRoute(address : InetSocketAddress) = path("SayHelloIp") {
  get {
    extractRequestEntity { entity =>
      entity(as[String]) { body =>
        complete(entity = s"Hello ${address.getAddress().getHostAddress()}")
      }
    }
  }
}

Http().bind(interface).runWith(Sink foreach { conn =>
  val address =  conn.remoteAddress

  conn.handleWithAsyncHandler(createRoute(address))
})

Edit

As noted in the comments: since akka 10.0.13 use conn.handleWith.

Ramón J Romero y Vigil
  • 17,373
  • 7
  • 77
  • 125
  • Hi Ramon, thank you for your answer! Unfortunately I still cannot get this to work. I have created the following program to test your answer: – ScalaNewbie Oct 20 '16 at 12:15
  • Sorry - I can't fit all my text as a response to your answer, so I will post it with the original question! – ScalaNewbie Oct 20 '16 at 12:16
  • I still can't get your example to compile. I get the compilation message: – ScalaNewbie Oct 20 '16 at 15:24
  • Error:(27,9) not found: value extractEntity – ScalaNewbie Oct 20 '16 at 15:24
  • extractEntity { entity => – ScalaNewbie Oct 20 '16 at 15:25
  • @ScalaNewbie changed the example code, should be `extractRequestEntity` – Ramón J Romero y Vigil Oct 20 '16 at 15:27
  • Sorry about the multiple comments being posted, every time I press return it posts the comment. But basically the issue is that extractEntity is not recognised by the Scala compiler. Is there a missing import? Or is it something to do with the version of Akka? I am using version 2.4.11 of akka-actor, akka-stream and akka-http-experimental. These are the most recent stable versions available right now. – ScalaNewbie Oct 20 '16 at 15:29
  • No need to apologize. `extractRequestEntity` should be coming from your `import akka.http.scaladsl.server.Directives._`. If you look at the api it's there: http://doc.akka.io/api/akka/2.4/?_ga=1.42089507.723935664.1469018756#akka.http.scaladsl.server.Directives – Ramón J Romero y Vigil Oct 20 '16 at 16:01
  • I obtained extractRequestEntity using an additional import: import akka.http.scaladsl.server.directives.BasicDirectives.extractRequestEntity . So now my compiler recognises the line with extractRequestEntity, but I still get (now different) error messages: Error(29, 17) akka.http.scaladsl.model.RequestEntity does not take parameters entity(as[String]) { body => Error(30,29) reassignment to val complete(entity = s"Hello ${address.getAddress().getHostAddress()}"). – ScalaNewbie Oct 20 '16 at 17:21
  • (I used because I can't put real linebreaks in Stackoverflow comments!) – ScalaNewbie Oct 20 '16 at 17:22
  • @ScalaNewbie It's impossible for me to solve your compile problems without having a full understanding of the build process. The import you mention is unnecessary. Maybe your akka version is old or something, but unfortunately the help I can give is limited by the info provided... – Ramón J Romero y Vigil Oct 20 '16 at 17:37
  • Thank you for your help so far. Does the example I quoted in the edit of the original question (with the build.sbt also) work for you? I agree it's impossible for you to help without understanding the build process, I am pretty sure I have the newest version of akka-http-experimental being imported, so I will go and examine the source code of akka-http-experimental to try to work out what is causing the compile errors. Thanks for your help over the last day or two! – ScalaNewbie Oct 20 '16 at 18:41
  • Hey, sorry to resurrect an old question, but will your answer still work in the current version of Akka HTTP? – Ali Oct 02 '18 at 03:16
  • 1
    @ClickUpvote No apologies necessary. As far as I can tell both the `extractClientIP` solution and the `bind` solution still conform to the most recent API... – Ramón J Romero y Vigil Oct 02 '18 at 12:16
  • With akka 10.0.13 use `handleWith` instead of `handleWithAsyncHandler` – gdomo Nov 16 '18 at 10:03
0

HttpExt.bind() has been depreciated. Here's a workaround with connectionSource():

val https: HttpsConnectionContext = /*...*/
Http(actorSystem).
  newServerAt(interface,httpsPort).
  enableHttps(https).
  connectionSource().
  to(Sink.foreach { connection =>
    println("New Connection from remote address :: ${connection.remoteAddress}")
    // handle connection here
  }).run().onComplete {
  case Success(binding)=>
    binding.addToCoordinatedShutdown(hardTerminationDeadline = 10.seconds)
    log.info("{} HTTPS Server running {}",errorContext,binding)
  case Failure(ex)=>
    log.error("{} HTTPS Server failed {}",errorContext,ex.toString)
    sys.exit(10)
}
rjmxtt
  • 23
  • 1
  • 1
  • 6