Closed murraytodd closed 4 years ago
I realize that an API change in something as central as ClientTransport.connectTo isn't something done lightly—but I figured it was worth consideration.
Changing the existing types is not an option because of compatibility guarantees Akka upholds. In reality what I'd advice in this specific case is to yes pass the file as the hostname and ignore the port.
What is the intended use and will end users even be exposed to this in what you are building?
ClientTransport
is marked @ApiMayChange
, so we could still change it.
If the socket file path is hardcoded, couldn't you otherwise just ignore the given host/port and create the connection directly?
But that still wouldn't help, because you still need an akka-streams API for unix domain sockets and ClientTransport
is only the first API that doesn't support iut. The next is the akka-streams TCP, then the Akka IO API and last not even the Java JDK supports unix domain sockets out of the box.
(I guess it could be supported with "moderate" complexity if we would open up Akka IO, and akka-streams API to use any kind of already connected java.nio.channels.SocketChannel
. https://github.com/jnr/jnr-unixsocket could then provide such a channel.)
@jrudolph There is support for unix domain sockets in Alpakka since a while though https://developer.lightbend.com/docs/alpakka/current/unix-domain-socket.html
For additional context, see this SO question: https://stackoverflow.com/questions/51565935/how-to-access-rest-api-on-a-unix-domain-socket-with-akka-http-or-alpakka
Thanks, @johanandren. I missed that somehow. So, maybe even without changing anything in akka-http for now we could provide an custom example ClientContext that achieves that result?
Here's a working snippet:
build.sbt:
val scalaV = "2.12.6"
val akkaV = "2.5.14"
val akkaHttpV = "10.1.3"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpV,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpV,
"com.typesafe.akka" %% "akka-stream" % akkaV,
"com.lightbend.akka" %% "akka-stream-alpakka-unix-domain-socket" % "0.20",
)
DockerSockMain.scala:
import java.io.File
import java.net.InetSocketAddress
import akka.actor.ActorSystem
import akka.http.scaladsl.ClientTransport
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.HttpRequest
import akka.http.scaladsl.model.HttpResponse
import akka.http.scaladsl.settings.ClientConnectionSettings
import akka.http.scaladsl.settings.ConnectionPoolSettings
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import akka.stream.alpakka.unixdomainsocket.scaladsl.UnixDomainSocket
import akka.stream.scaladsl.Flow
import akka.util.ByteString
import spray.json.JsValue
import scala.concurrent.Future
object DockerSockMain extends App {
object DockerSockTransport extends ClientTransport {
override def connectTo(host: String, port: Int, settings: ClientConnectionSettings)(implicit system: ActorSystem): Flow[ByteString, ByteString, Future[Http.OutgoingConnection]] = {
// ignore everything for now
UnixDomainSocket().outgoingConnection(new File("/var/run/docker.sock"))
.mapMaterializedValue { _ =>
// Seems that the UnixDomainSocket.OutgoingConnection is never completed? It works anyway if we just assume it is completed
// instantly
Future.successful(Http.OutgoingConnection(InetSocketAddress.createUnresolved(host, port), InetSocketAddress.createUnresolved(host, port)))
}
}
}
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
import system.dispatcher
val settings = ConnectionPoolSettings(system).withTransport(DockerSockTransport)
import SprayJsonSupport._
def handleResponse(response: HttpResponse): Future[String] =
// TODO: create docker json model classes and directly marshal to them
Unmarshal(response).to[JsValue].map(_.prettyPrint)
Http().singleRequest(HttpRequest(uri = "http://localhost/images/json"), settings = settings)
.flatMap(handleResponse)
.onComplete { res =>
println(s"Got result: [$res]")
system.terminate()
}
}
The problem is that what we get from URIs is host/port so the interface matches what is needed in general. Since the workaround works nicely for docker, let's close this for now.
I am considering trying to write a ClientTransport implementation to enable Akka HTTP to access a service leveraging Unix Domain Sockets. (Specifically, the Docker API is exposed on /var/run/docker.sock and uses HTTP REST)
Looking at the ClientTransport trait that defines the general API, connectTo assumes a hostname/port pairing which is tightly coupled to the TCP implementation. If one were to create a Unix Domain Sockets implementation, it would require some inelegant approaches like forcing the port parameter to be supplied but ignored, and for the filename to be passed in the named "host" parameter, etc.
I realize that an API change in something as central as ClientTransport.connectTo isn't something done lightly—but I figured it was worth consideration. If there's a better way for an external (Alpakka) module to deal with this (like implementing connectTo but either making the hack or returning an exception directing the developer to use an alternative method call), please let me know.