{"id":309195,"date":"2020-08-30T03:00:23","date_gmt":"2020-08-30T03:00:23","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=309195"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=309195","title":{"rendered":"\u0418\u0437\u0443\u0447\u0430\u044e Scala: \u0427\u0430\u0441\u0442\u044c 4 \u2014 WebSocket"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/517076\/\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/uz\/bf\/xv\/uzbfxvf-qde28xlfesmd8xmkm2e.png\"><br \/>  \u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041d\u0430 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u044f \u043f\u043e \u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0447\u0430\u0442 \u0447\u0435\u0440\u0435\u0437 \u0412\u0435\u0431\u0421\u043e\u043a\u0435\u0442\u044b. \u0417\u0430 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0434\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043a\u0430\u0442.<br \/>  <a name=\"habracut\"><\/a>  <\/p>\n<h2>\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435<\/h2>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/habr.com\/ru\/post\/503560\/\">\u0418\u0437\u0443\u0447\u0430\u044e Scala: \u0427\u0430\u0441\u0442\u044c 1 \u2014 \u0418\u0433\u0440\u0430 \u0437\u043c\u0435\u0439\u043a\u0430<\/a><\/li>\n<li><a href=\"https:\/\/habr.com\/ru\/post\/508560\/\">\u0418\u0437\u0443\u0447\u0430\u044e Scala: \u0427\u0430\u0441\u0442\u044c 2 \u2014 Todo \u043b\u0438\u0441\u0442 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a<\/a><\/li>\n<li><a href=\"https:\/\/habr.com\/ru\/post\/516154\/\">\u0418\u0437\u0443\u0447\u0430\u044e Scala: \u0427\u0430\u0441\u0442\u044c 3 \u2014 \u042e\u043d\u0438\u0442 \u0422\u0435\u0441\u0442\u044b<\/a><\/li>\n<li>\u0418\u0437\u0443\u0447\u0430\u044e Scala: \u0427\u0430\u0441\u0442\u044c 4 \u2014 WebSocket<\/li>\n<\/ul>\n<p>  <\/p>\n<h2>\u0421\u0441\u044b\u043b\u043a\u0438<\/h2>\n<p>  <\/p>\n<ol>\n<li><a href=\"https:\/\/gitlab.com\/VictorWinbringer\/scalatodoapi\" rel=\"nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438<\/a><\/li>\n<li><a href=\"https:\/\/gitlab.com\/VictorWinbringer\/scalatodoapi\/container_registry\/\" rel=\"nofollow\">\u041e\u0431\u0440\u0430\u0437\u044b docker image<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/softwaremill\/tapir\" rel=\"nofollow\">Tapir<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/http4s\/http4s\" rel=\"nofollow\">Http4s<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/typelevel\/fs2\" rel=\"nofollow\">Fs2<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/tpolecat\/doobie\" rel=\"nofollow\">Doobie<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/scalatest\/scalatest\" rel=\"nofollow\">ScalaTest<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/typelevel\/scalacheck\" rel=\"nofollow\">ScalaCheck<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/scalatest\/scalatestplus-scalacheck\" rel=\"nofollow\">ScalaTestPlusScalaCheck<\/a><\/li>\n<\/ol>\n<p>  \u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u0435\u0441\u044c \u043a\u043e\u0434 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u043e\u0434\u043d\u043e\u043c \u043e\u0431\u044a\u0435\u043a\u0442 ChatHub   <\/p>\n<pre><code class=\"scala\">class ChatHub[F[_]] private(                              val topic: Topic[F, WebSocketFrame],                              private val ref: Ref[F, Int]                            )                            (                              implicit concurrent: Concurrent[F],                              timer: Timer[F]                            ) extends Http4sDsl[F] {    val endpointWs: ServerEndpoint[String, Unit, String, Stream[IO, WebSocketFrame], IO] = endpoint     .get     .in(&quot;chat&quot;)     .tag(&quot;WebSockets&quot;)     .summary(&quot;\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0441\u044f \u043a \u043e\u0431\u0449\u0435\u043c\u0443 \u0447\u0430\u0442\u0443. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e \u0442\u0430\u043a\u043e\u043c\u0443 \u0430\u0434\u0440\u0435\u0441\u0443: ws:\/\/localhost:8080\/chat&quot;)     .description(&quot;\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043a \u043e\u0431\u0449\u0435\u043c\u0443 \u0447\u0430\u0442\u0443&quot;)     .in(       stringBody         .description(&quot;\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0432 \u0447\u0430\u0442\u0435&quot;)         .example(&quot;\u041f\u0440\u0438\u0432\u0435\u0442!&quot;)     )     .out(       stringBody         .description(&quot;\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043a\u0442\u043e-\u0442\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0432 \u0447\u0430\u0442&quot;)         .example(&quot;6 : \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0441 Id \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f f518a53d: \u041f\u0440\u0438\u0432\u0435\u0442!&quot;)     )     \/\/\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u043e\u0439.      .serverLogic(_ =&gt; IO(Left(()): Either[Unit, String]))    def routeWs: HttpRoutes[F] = {     HttpRoutes.of[F] {       case GET -&gt; Root \/ &quot;chat&quot; =&gt; logic()     }   }    private def logic(): F[Response[F]] = {     val toClient: Stream[F, WebSocketFrame] =       topic.subscribe(1000)     val fromClient: Pipe[F, WebSocketFrame, Unit] =       handle     WebSocketBuilder[F].build(toClient, fromClient)   }    private def handle(s: Stream[F, WebSocketFrame]): Stream[F, Unit] = s     .collect({       case WebSocketFrame.Text(text, _) =&gt; text     })     .evalMap(text =&gt; ref.modify(count =&gt; (count + 1, WebSocketFrame.Text(s&quot;${count + 1} : $text&quot;))))     .through(topic.publish) }  object ChatHub {    def apply[F[_]]()(implicit concurrent: Concurrent[F], timer: Timer[F]): F[ChatHub[F]] = for {     ref &lt;- Ref.of[F, Int](0)     topic &lt;- Topic[F, WebSocketFrame](WebSocketFrame.Text(&quot;===&quot;))   } yield new ChatHub(topic, ref) } <\/code><\/pre>\n<p>  \u0422\u0443\u0442 \u043d\u0430\u0434\u043e \u0441\u0440\u0430\u0437\u0443 \u0441\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0440\u043e Topic \u2014 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0437 Fs2 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u044c Publisher \u2014 Subscriber \u043f\u0440\u0438\u0447\u0435\u043c \u0443 \u0432\u0430\u0441 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043c\u043d\u043e\u0433\u043e Publisher \u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043c\u043d\u043e\u0433\u043e Subscriber. \u0412\u043e\u043e\u0431\u0449\u0435 \u0432 \u043d\u0435\u0433\u043e \u043b\u0443\u0447\u0448\u0435\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0431\u0443\u0444\u0435\u0440 \u0432\u0440\u043e\u0434\u0435 Queue \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0443 \u043d\u0435\u0433\u043e \u0435\u0441\u0442\u044c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043d\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u0438 Publisher \u0436\u0434\u0435\u0442 \u043f\u043e\u043a\u0430 \u0432\u0441\u0435 Subscriber \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u0435\u0441\u043b\u0438 \u043e\u043d\u0430 \u043f\u0435\u0440\u0435\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0438 \u0437\u0430\u0432\u0438\u0441\u043d\u0443\u0442\u044c.  <\/p>\n<pre><code class=\"scala\">val topic: Topic[F, WebSocketFrame], <\/code><\/pre>\n<p>  \u0422\u0443\u0442 \u0435\u0449\u0435 \u044f \u0441\u0447\u0438\u0442\u0430\u044e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u044b\u043b\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432 \u0447\u0430\u0442 \u043a\u0430\u043a \u043d\u043e\u043c\u0435\u0440 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u0422\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u043e \u043c\u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0437 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u043e\u0432 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0430\u043d\u0430\u043b\u043e\u0433 Atomic \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0442\u0443\u0442 \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f Ref \u0438 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0441\u0442\u044c \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438.  <\/p>\n<pre><code class=\"scala\">  private val ref: Ref[F, Int] <\/code><\/pre>\n<p>  \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043f\u043e\u0442\u043e\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.  <\/p>\n<pre><code class=\"scala\">  private def handle(stream: Stream[F, WebSocketFrame]): Stream[F, Unit] =      stream \/\/\u0414\u043e\u0441\u0442\u0430\u0435\u043c \u0438\u0437 \u0444\u0440\u0435\u0439\u043c\u0430 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c \u0444\u0440\u0435\u0439\u043c\u044b.      .collect({       case WebSocketFrame.Text(text, _) =&gt; text     }) \/\/\u0410\u0442\u043e\u043c\u0430\u0440\u043d\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u043d\u0430\u0448 \u0441\u0447\u0435\u0442\u0447\u0438\u043a \u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u043d\u043e\u0432\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043a \u0442\u0435\u043a\u0441\u0442\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.     .evalMap(text =&gt; ref.modify(count =&gt; (count + 1, WebSocketFrame.Text(s&quot;${count + 1} : $text&quot;)))) \/\/\u041a\u0430\u0436\u0434\u043e\u0435 \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0435\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0442\u043e\u043f\u0438\u043a     .through(topic.publish) <\/code><\/pre>\n<p>  \u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u0430\u043c\u0430 \u043b\u043e\u0433\u0438\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0441\u043e\u043a\u0435\u0442\u0430.  <\/p>\n<pre><code class=\"scala\">private def logic(): F[Response[F]] = { \/\/\u041e\u0442\u043a\u0443\u0434\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430.     val toClient: Stream[F, WebSocketFrame] = \/\/\u041f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u0442\u043e\u043f\u0438\u043a       topic.subscribe(1000) \/\/\u0427\u0442\u043e \u0431\u0443\u0434\u0435\u043c \u0434\u0435\u043b\u0430\u0442\u044c \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430     val fromClient: Pipe[F, WebSocketFrame, Unit] = \/\/\u041f\u0440\u043e\u0441\u0442\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0442\u043e\u043f\u0438\u043a \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438       handle \/\/\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0435\u0431 \u0441\u043e\u043a\u0435\u0442 \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0440\u0430\u043d\u0435\u0435 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u043c \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0445.     WebSocketBuilder[F].build(toClient, fromClient)   } <\/code><\/pre>\n<p>  \u0421\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u043d\u0430\u0448 \u0441\u043e\u043a\u0435\u0442 \u0441 \u0440\u043e\u0443\u0442\u043e\u043c \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 (ws:\/\/localhost:8080\/chat)  <\/p>\n<pre><code class=\"scala\">def routeWs: HttpRoutes[F] = {     HttpRoutes.of[F] {       case GET -&gt; Root \/ &quot;chat&quot; =&gt; logic()     }   } <\/code><\/pre>\n<p>  \u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043d\u0430 \u044d\u0442\u043e\u043c \u0432\u0441\u0435. \u0414\u0430\u043b\u044c\u0448\u0435 \u0443\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u0441 \u044d\u0442\u0438\u043c \u0440\u043e\u0443\u0442\u043e\u043c. \u041c\u043d\u0435 \u0435\u0449\u0435 \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u043a\u0430\u043a\u0443\u044e \u043d\u0438 \u043a\u0430\u043a\u0443\u044e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u0441\u0434\u0435\u043b\u0430\u0442\u044c. \u0412\u043e\u043e\u0431\u0449\u0435 \u0434\u043b\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f WebSocket \u0438 \u043f\u0440\u043e\u0447\u0435\u0433\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0432\u0440\u043e\u0434\u0435 RabbitMQ AMPQ \u0435\u0441\u0442\u044c <a href=\"https:\/\/www.asyncapi.com\/\" rel=\"nofollow\">AsynAPI<\/a> \u043d\u043e \u043f\u043e\u0434 Tapir \u0442\u0430\u043c \u043d\u0435\u0442 \u043d\u0438\u0447\u0435\u0433\u043e \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b \u0434\u043b\u044f Swagger \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u044d\u043d\u0434\u043f\u043e\u0439\u043d\u0442\u0430 \u043a\u0430\u043a GET \u0437\u0430\u043f\u0440\u043e\u0441. \u0420\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043e\u043d \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u043d\u0435 \u0431\u0443\u0434\u0435\u0442. \u0422\u043e\u0447\u043d\u0435\u0435 501 \u043e\u0448\u0438\u0431\u043a\u0443 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0437\u0430\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432 Swagger  <\/p>\n<pre><code class=\"scala\">  val endpointWs: Endpoint[String, Unit, String, fs2.Stream[F, Byte]] = endpoint     .get     .in(&quot;chat&quot;)     .tag(&quot;WebSockets&quot;)     .summary(&quot;\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0441\u044f \u043a \u043e\u0431\u0449\u0435\u043c\u0443 \u0447\u0430\u0442\u0443. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e \u0442\u0430\u043a\u043e\u043c\u0443 \u0430\u0434\u0440\u0435\u0441\u0443: ws:\/\/localhost:8080\/chat&quot;)     .description(&quot;\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043a \u043e\u0431\u0449\u0435\u043c\u0443 \u0447\u0430\u0442\u0443&quot;)     .in(       stringBody         .description(&quot;\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0432 \u0447\u0430\u0442\u0435&quot;)         .example(&quot;\u041f\u0440\u0438\u0432\u0435\u0442!&quot;)     )     .out(       stringBody         .description(&quot;\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043a\u0442\u043e-\u0442\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0432 \u0447\u0430\u0442&quot;)         .example(&quot;6 : \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0441 Id \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f f518a53d: \u041f\u0440\u0438\u0432\u0435\u0442!&quot;)     ) <\/code><\/pre>\n<p>  \u0412 \u0441\u0430\u043c\u043e\u043c \u0441\u0432\u0430\u0433\u0433\u0435\u0440\u0435 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432\u043e\u0442 \u0442\u0430\u043a<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/8f\/if\/hi\/8fifhiscqbs_5ef3jizegscza5a.png\"><br \/>  \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043d\u0430\u0448 \u0447\u0430\u0442 \u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0443 API   <\/p>\n<pre><code class=\"scala\">    todosController = new TodosController()     imagesController = new ImagesController() \/\/\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043d\u0430\u0448\u0435\u0433\u043e \u0447\u0430\u0442\u0430     chatHub &lt;- Resource.liftF(ChatHub[IO]())     endpoints = todosController.endpoints ::: imagesController.endpoints \/\/\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0433\u043e \u044d\u043d\u0434\u043f\u043e\u0439\u043d\u0442 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e Swagger     docs = (chatHub.endpointWs :: endpoints).toOpenAPI(&quot;The Scala Todo List&quot;, &quot;0.0.1&quot;)     yml: String = docs.toYaml \/\/\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0432 \u0441\u043f\u0438\u0441\u043e\u043a \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f     routes = chatHub.routeWs &lt;+&gt;       endpoints.toRoutes &lt;+&gt;       new SwaggerHttp4s(yml, &quot;swagger&quot;).routes[IO]     httpApp = Router(       &quot;\/&quot; -&gt; routes     ).orNotFound     blazeServer &lt;- BlazeServerBuilder[IO](serverEc)       .bindHttp(settings.host.port, settings.host.host)       .withHttpApp(httpApp)       .resource <\/code><\/pre>\n<p>  \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u0447\u0430\u0442\u0443 \u043a\u0440\u0430\u0439\u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u043c.  <\/p>\n<pre><code class=\"javascript\">    &lt;script&gt;         const id = `f${(~~(Math.random() * 1e8)).toString(16)}`;         const webSocket = new WebSocket('ws:\/\/localhost:8080\/chat');          webSocket.onopen = event =&gt; {             alert('onopen ');         };          webSocket.onmessage = event =&gt; {             console.log(event);             receive(event.data);         };          webSocket.onclose = event =&gt; {             alert('onclose ');         };          function send() {             let text = document.getElementById(&quot;message&quot;);             webSocket.send(`\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0441 Id \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f ${id}: ${text.value}`);             text.value = '';         }          function receive(m) {             let text = document.getElementById(&quot;chat&quot;);             text.value = text.value + '\\n\\r' + m;         }     &lt;\/script&gt; <\/code><\/pre>\n<p>  \u041d\u0430 \u044d\u0442\u043e\u043c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u0441\u0435. \u041d\u0430\u0434\u0435\u044e\u0441\u044c \u043a\u043e\u043c\u0443-\u0442\u043e \u043a\u0442\u043e \u0442\u043e\u0436\u0435 \u0438\u0437\u0443\u0447\u0430\u0435\u0442 \u0441\u043a\u0430\u043b\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430 \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u0430 \u043c\u043e\u0436\u0435\u0442 \u0434\u0430\u0436\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u0430.<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/517076\/\"> https:\/\/habr.com\/ru\/post\/517076\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/517076\/\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/uz\/bf\/xv\/uzbfxvf-qde28xlfesmd8xmkm2e.png\"><br \/>  \u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041d\u0430 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u044f \u043f\u043e \u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0447\u0430\u0442 \u0447\u0435\u0440\u0435\u0437 \u0412\u0435\u0431\u0421\u043e\u043a\u0435\u0442\u044b. \u0417\u0430 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0434\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043a\u0430\u0442.  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-309195","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/309195","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=309195"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/309195\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=309195"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=309195"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=309195"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}