如何使用 Akka HTTP 或 Alpakka 在 unix 域套接字上访问 REST API?
How to access REST API on a unix domain socket with Akka HTTP or Alpakka?
我想使用 /var/lib/docker.sock unix 域套接字访问 docker API。我看过一些示例,您可以使用(现代版本的)curl 调用 API,如下所示:
curl --unix-socket /var/run/docker.sock http:/containers/json
其中 REST 命令在 /containers/json 路径中表示。我很高兴看到 Alpakka Unix 域套接字适配器,但您似乎只能发送和接收原始字节。有什么优雅的方法可以做到这一点吗?还是我必须手动构建一个 HTTP header 并手动管理所有困难的东西?
有趣的用例。您应该能够使用 Alpakka Unix 域套接字流并将 Akka Http ClientLayer 放在其上。
问题的简短答案是 "It Can't be Done"——至少 Akka HTTP 和 Alkappa Unix 域套接字的现有构建块不是这样。您必须通过手动发送 headers 来处理编写 HTTP GET 请求,即(使用 Docker API 作为示例)
GET /v1.24/containers/json HTTP/1.1\n
Host: localhost\n
\n\n
...然后手动读取 TCP 响应。此外,Unix 域套接字逻辑不能使用 Alpakka 代码,因为它目前仅提供一个 ServerBinding,因此旨在创建一个服务器来处理对 Unix 套接字的请求,不是将数据发送到 Unix 套接字并处理响应。
所以一切都必须手动完成。还有另一个 Whosebug question here 指出了如何使用 AFUNIXSocket github 源代码来帮助处理一些 low-level Unix 域套接字逻辑,这可能对其他想要解决这个问题的人有帮助问题。
最优雅的解决方案还包括(如 dvim 的评论所建议的那样)编写一个 HTTP.ClientTransport 以插入 Unix 域套接字通信层并允许 HTTP 库公开 low-level 的功能写作 request/response headers 等。(一个有趣的注意事项是 API 假定一个 host/port 参数对,它与 TCP 范例紧密绑定。)
这是一个工作片段(另请参阅 akka/akka-http#2139 上的其余讨论):
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()
}
}
我想使用 /var/lib/docker.sock unix 域套接字访问 docker API。我看过一些示例,您可以使用(现代版本的)curl 调用 API,如下所示:
curl --unix-socket /var/run/docker.sock http:/containers/json
其中 REST 命令在 /containers/json 路径中表示。我很高兴看到 Alpakka Unix 域套接字适配器,但您似乎只能发送和接收原始字节。有什么优雅的方法可以做到这一点吗?还是我必须手动构建一个 HTTP header 并手动管理所有困难的东西?
有趣的用例。您应该能够使用 Alpakka Unix 域套接字流并将 Akka Http ClientLayer 放在其上。
问题的简短答案是 "It Can't be Done"——至少 Akka HTTP 和 Alkappa Unix 域套接字的现有构建块不是这样。您必须通过手动发送 headers 来处理编写 HTTP GET 请求,即(使用 Docker API 作为示例)
GET /v1.24/containers/json HTTP/1.1\n
Host: localhost\n
\n\n
...然后手动读取 TCP 响应。此外,Unix 域套接字逻辑不能使用 Alpakka 代码,因为它目前仅提供一个 ServerBinding,因此旨在创建一个服务器来处理对 Unix 套接字的请求,不是将数据发送到 Unix 套接字并处理响应。
所以一切都必须手动完成。还有另一个 Whosebug question here 指出了如何使用 AFUNIXSocket github 源代码来帮助处理一些 low-level Unix 域套接字逻辑,这可能对其他想要解决这个问题的人有帮助问题。
最优雅的解决方案还包括(如 dvim 的评论所建议的那样)编写一个 HTTP.ClientTransport 以插入 Unix 域套接字通信层并允许 HTTP 库公开 low-level 的功能写作 request/response headers 等。(一个有趣的注意事项是 API 假定一个 host/port 参数对,它与 TCP 范例紧密绑定。)
这是一个工作片段(另请参阅 akka/akka-http#2139 上的其余讨论):
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()
}
}