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
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With