Как получить доступ к REST API в сокете домена unix с помощью Akka HTTP или Alpakka?

Я хотел бы получить доступ к API докеров, используя сокет домена /var/lib/docker.sock unix. Я видел примеры, в которых вы можете использовать (современные версии) curl для вызова API следующим образом:

curl --unix-socket /var/run/docker.sock http:/containers/json

где команда REST выражена в пути / container / json. Я был взволнован, увидев адаптер Alpakka Unix Domain Socket, но похоже, что вы можете отправлять и получать только необработанные байты. Есть ли какой-нибудь изящный способ сделать это? Или мне нужно вручную создать HTTP-заголовок и управлять всеми сложными вещами вручную?


person Murray Todd Williams    schedule 27.07.2018    source источник


Ответы (3)


Вот рабочий фрагмент (см. Также остальную часть обсуждения на странице https://github.com/akka/akka-http/issues/2139#issuecomment-413535497):

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()
    }
}
person jrudolph    schedule 16.08.2018
comment
Ух ты! Я очень ценю время, которое вы потратили на это. Я ломал голову, и в итоге попытался вручную кодировать протокол http, а затем понял, сколько крайних случаев мне придется обрабатывать ... гораздо лучше придерживаться этого пути и получить библиотеки работать вместе. Я постараюсь вставить этот фрагмент вместо того, что пытался, и дам вам знать, как он работает. (Когда я вернусь из поездки, через две недели.) - person Murray Todd Williams; 17.08.2018

Интересный вариант использования. Вы должны иметь возможность использовать поток сокетов Alpakka Unix Domain и поместить Akka Http ClientLayer поверх него.

person dvim    schedule 28.07.2018
comment
Спасибо за первоначальное направление. Если есть какие-то дополнительные хлебные крошки, которые вы (или кто-либо) можете сбросить, это будет полезно. Я просматривал исходный код уже несколько часов и размышлял над этим. Насколько я понимаю ваше предложение, это использовать Bidi ClientLayer и каким-то образом подключать потоки SslTlsOutbound и SslTlsInbound к UnixDomainSocket (поскольку нет уровня SSL, должен быть способ использовать фиктивный, который просто передает данные через Flow [ ByteString, ByteString, Future [OutgoingConnection]] абстракция, используемая UnixDomainSocket.outgoingConnection. - person Murray Todd Williams; 06.08.2018
comment
Взгляните на TLS Placebo BidiFlow, который разворачивает сообщения SslTls * в ByteStrings без какого-либо шифрования. Обратите внимание, что это часть проекта akka-stream, который находится в репозитории akka / akka. Также вы можете реализовать akka-http ClientTransport SPI для сокета домена unix - person dvim; 07.08.2018
comment
Спасибо за эти дополнительные панировочные сухари. Это было интересное путешествие (я много узнаю об Akka Streams, глядя на исходный код!) Я считаю, что теперь полностью понимаю проблему. (См. Мой ответ ниже.) - person Murray Todd Williams; 07.08.2018

Краткий ответ на вопрос: «Это невозможно сделать», по крайней мере, с существующими строительными блоками Akka HTTP и доменными сокетами Alkappa Unix. Вам нужно будет обработать запись HTTP-запроса GET, вручную отправив заголовки, т.е. (используя Docker API в качестве примера)

GET /v1.24/containers/json HTTP/1.1\n
Host: localhost\n
\n\n

... а затем вручную прочитать ответ TCP. Кроме того, логика сокета домена Unix не может использовать код Alpakka, потому что в настоящее время она предоставляет только ServerBinding и, таким образом, предназначена для создания сервера, который обрабатывает запросы к сокету Unix, не для отправки данных на сокет Unix и обработать ответ.

Так что все приходится делать вручную. Есть еще один вопрос StackOverflow здесь, который указывает, как AFUNIXSocket github Исходный код может быть использован для помощи с некоторой низкоуровневой логикой Unix Domain Socket, которая может помочь другим, желающим решить эту же проблему.

Наиболее элегантное решение также будет включать (как было предложено в комментарии dvim) написание HTTP.ClientTransport для подключения уровня связи Unix Domain Socket и позволяющего библиотеке HTTP предоставлять низкоуровневую функциональность записи заголовков запросов / ответов и т. Д. ( Одно интересное замечание по этому поводу заключается в том, что API предполагает наличие пары параметров хост / порт, которая жестко привязана к парадигме TCP.)

person Murray Todd Williams    schedule 07.08.2018