Webové zásuvky s rámcom Play a Akkou

1. Prehľad

Ak chceme, aby naši weboví klienti udržiavali dialóg s našim serverom, môžu byť užitočné riešenia WebSockets. Webové zásuvky udržujú trvalé úplné duplexné pripojenie. Toto nám dáva schopnosť posielať obojsmerné správy medzi našim serverom a klientom.

V tomto tutoriáli sa naučíme, ako používať WebSockets s Akkou v rámci Play Framework.

2. Inštalácia

Poďme si nastaviť jednoduchú chatovú aplikáciu. Používateľ odošle správy na server a server odpovie správou od JSONPlaceholder.

2.1. Nastavenie aplikácie Play Framework

Túto aplikáciu zostavíme pomocou rámca Play.

Podľa pokynov v časti Úvod do hry v Jave nastavíme a spustíme jednoduchú aplikáciu Play Framework.

2.2. Pridanie potrebných súborov JavaScript

Pri skriptovaní na strane klienta tiež budeme musieť pracovať s jazykom JavaScript. Toto nám umožní prijímať nové správy odoslané zo servera. Použijeme na to knižnicu jQuery.

Pridajme jQuery na koniec aplikácia / zobrazenia / index.scala.html spis:

2.3. Nastavenie Akka

Nakoniec použijeme Akku na spracovanie pripojení WebSocket na strane servera.

Prejdime k build.sbt súbor a pridať závislosti.

Musíme pridať akka-herec a akka-testkit závislosti:

libraryDependencies + = "com.typesafe.akka" %% "akka-actor"% akkaVersion libraryDependencies + = "com.typesafe.akka" %% "akka-testkit"% akkaVersion

Potrebujeme ich, aby sme mohli používať a testovať kód rámca Akka.

Ďalej budeme používať prúdy Akka. Pridajme teda akka-prúd závislosť:

libraryDependencies + = "com.typesafe.akka" %% "akka-stream"% akkaVersion

Na záver musíme nazvať oddychový koncový bod od herca Akky. Na to budeme potrebovať akka-http závislosť. Keď to urobíme, koncový bod vráti údaje JSON, ktoré budeme musieť rekonštruovať, takže musíme pridať akka-http-jackson závislosť tiež:

libraryDependencies + = "com.typesafe.akka" %% "akka-http-jackson"% akkaHttpVersion libraryDependencies + = "com.typesafe.akka" %% "akka-http"% akkaHttpVersion

A teraz sme všetci pripravení. Pozrime sa, ako uviesť do činnosti webové zásuvky!

3. Zaobchádzanie s webovými zásuvkami s aktérmi Akka

Mechanizmus manipulácie s webovou zásuvkou Play je založený na streamoch Akka. WebSocket je modelovaný ako tok. Takže prichádzajúce správy WebSocket sa privádzajú do toku a správy produkované tokom sa odosielajú klientovi.

Aby sme mohli pracovať s WebSocket pomocou herca, budeme potrebovať pomôcku Play ActorFlow ktorý prevádza ActorRef do toku. To si vyžaduje hlavne nejaký kód Java s malou konfiguráciou.

3.1. Metóda radiča WebSocket

Najprv potrebujeme a Materializátor inštancia. Materializer je továreň na motory na vykonávanie streamov.

Musíme si podať injekciu ActorSystem a Materializátor do ovládača app / controllers / HomeController.java:

private ActorSystem actorSystem; súkromný materializér materializátora; @Inject public HomeController (ActorSystem actorSystem, Materializer materializer) {this.actorSystem = actorSystem; this.materializer = materializátor; }

Teraz pridajme metódu radiča soketov:

public WebSocket socket () {return WebSocket.Json .acceptOrResult (this :: createActorFlow); }

Tu voláme funkciu acceptOrResult ktorý vezme hlavičku požiadavky a vráti budúcnosť. Vrátenou budúcnosťou je tok na spracovanie správ WebSocket.

Namiesto toho môžeme žiadosť zamietnuť a vrátiť výsledok zamietnutia.

Teraz vytvorme tok:

súkromné ​​dokončenie<>> createActorFlow (požiadavka Http.RequestHeader) {návrat CompletableFuture.completedFuture (F.Either.Right (createFlowForActor ())); }

The F triedy v rozhraní Play Framework definuje množinu pomocníkov štýlu funkčného programovania. V tomto prípade používame F.Buď. Správne prijať spojenie a vrátiť tok.

Povedzme, že sme chceli odmietnuť pripojenie, keď klient nie je autentifikovaný.

Za týmto účelom by sme mohli skontrolovať, či je v relácii nastavené používateľské meno. Ak to tak nie je, odmietneme spojenie s protokolom HTTP 403 Forbidden:

súkromné ​​dokončenie<>> createActorFlow2 (požiadavka Http.RequestHeader) {návrat CompletableFuture.completedFuture (request.session () .getOptional ("používateľské meno"). mapa (používateľské meno -> F. Buď.Right (createFlowForActor ())) .orElseGet (() -> F.Either.Left (zakázané ()))); }

Používame F. Buď. Vľavo odmietnuť spojenie rovnakým spôsobom, ako poskytujeme postup F. Buď. Správne.

Nakoniec prepojíme tok s aktérom, ktorý bude správy spracovávať:

private Flow createFlowForActor () {return ActorFlow.actorRef (out -> Messenger.props (out), actorSystem, materializer); }

The ActorFlow.actorRef vytvára tok, ktorý je riešený pomocou Messenger herec.

3.2. The trás Súbor

Teraz pridajme trás definície metód radiča v conf / trasy:

GET / controllers.HomeController.index (požiadavka: žiadosť) GET / chat controllers.HomeController.socket GET / chat / s / streams controllers.HomeController.akkaStreamsSocket GET / assets / * file controllers.Assets.versioned (cesta = "/ verejná") , file: Asset)

Tieto definície smerovania mapujú prichádzajúce požiadavky HTTP na metódy akcie radiča, ako je vysvetlené v časti Smerovanie v aplikáciách Play v Jave.

3.3. Herecká implementácia

Najdôležitejšou súčasťou hereckej triedy je createReceive metóda ktorá určuje, ktoré správy môže herec spracovať:

@Override public Príjem createReceive () {návrat receiveBuilder () .match (JsonNode.class, this :: onSendMessage) .matchAny (o -> log.error ("Prijatá neznáma správa: {}", o.getClass ())) .build (); }

Herec preposiela všetky správy zodpovedajúce JsonNode triedy do onSendMessage metóda obsluhy:

private void onSendMessage (JsonNode jsonNode) {RequestDTO requestDTO = MessageConverter.jsonNodeToRequest (jsonNode); Reťazcová správa = requestDTO.getMessage (). ToLowerCase (); // .. processMessage (requestDTO); }

Potom bude obsluha reagovať na každú správu pomocou processMessage metóda:

private void processMessage (RequestDTO requestDTO) {CompletionStage responseFuture = getRandomMessage (); responseFuture.thenCompose (this :: consumeHttpResponse) .thAcAc (messageDTO -> out.tell (MessageConverter.messageToJsonNode (messageDTO), getSelf ())); }

3.4. Náročné odpočinkové API s protokolom Akka HTTP

Požiadavky HTTP pošleme generátorovi fiktívnych správ na JSONPlaceholder Posts. Keď príde odpoveď, pošleme odpoveď klientovi tak, že ju napíšeme von.

Poďme mať metódu, ktorá volá koncový bod s náhodným ID príspevku:

private CompletionStage getRandomMessage () {int postId = ThreadLocalRandom.current (). nextInt (0, 100); vrátiť Http.get (getContext (). getSystem ()) .singleRequest (HttpRequest.create ("//jsonplaceholder.typicode.com/posts/" + postId)); }

Spracovávame tiež HttpResponse dostaneme z volania služby, aby sme dostali odpoveď JSON:

private CompletionStage consumeHttpResponse (HttpResponse httpResponse) {Materializer materializer = Materializer.matFromSystem (getContext (). getSystem ()); návrat Jackson.unmarshaller (MessageDTO.class) .unmarshal (httpResponse.entity (), materializer) .thAApply (messageDTO -> {log.info ("prijatá správa: {}", messageDTO); discardEntity (httpResponse, materializer); návrat messageDTO;}); }

The MessageConverter class je utilita na konverziu medzi JsonNode a DTO:

public static MessageDTO jsonNodeToMessage (JsonNode jsonNode) {ObjectMapper mapper = nový ObjectMapper (); return mapper.convertValue (jsonNode, MessageDTO.class); }

Ďalej musíme entitu zahodiť. The discardEntityBytes metóda pohodlia slúži na ľahké vyradenie subjektu, ak to pre nás nemá žiadny účel.

Pozrime sa, ako zahodiť bajty:

private void discardEntity (HttpResponse httpResponse, Materializer materializer) {HttpMessage.DiscardedEntity discarded = httpResponse.discardEntityBytes (materializer); discarded.completionStage () .whenComplete ((done, ex) -> log.info ("Entita bola úplne zahodená!")); }

Po dokončení manipulácie s WebSocketom sa pozrime, ako pre to môžeme nastaviť klienta pomocou WebSocketov HTML5.

4. Nastavenie klienta WebSocket

Pre nášho klienta vytvorme jednoduchú webovú aplikáciu na chatovanie.

4.1. Činnosť kontrolóra

Musíme definovať činnosť radiča, ktorá vykreslí indexovú stránku. Dáme to do triedy ovládačov app.controllers.HomeController:

verejný index výsledkov (požiadavka Http.Request) {reťazec url = trasy.HomeController.socket () .webSocketURL (požiadavka); návrat ok (views.html.index.render (url)); } 

4.2. Stránka so šablónou

Teraz poďme k app / views / ndex.scala.html stránku a pridajte kontajner pre prijaté správy a formulár na zachytenie novej správy:

 F Odoslať 

Budeme tiež musieť zadať adresu URL akcie radiča WebSocket vyhlásením tohto parametra v hornej časti okna app / views / index.scala.htmlstránka:

@ (adresa URL: reťazec)

4.3. Obslužné rutiny udalostí WebSocket v JavaScripte

A teraz môžeme pridať JavaScript na spracovanie udalostí WebSocket. Pre jednoduchosť pridáme funkcie JavaScript v dolnej časti okna app / views / index.scala.html stránke.

Vyhlásime obslužné rutiny udalostí:

var webSocket; var messageInput; funkcia init () {initWebSocket (); } funkcia initWebSocket () {webSocket = nový WebSocket ("@ url"); webSocket.onopen = onOpen; webSocket.onclose = onClose; webSocket.onmessage = onMessage; webSocket.onerror = onError; }

Pridajme samotné manipulátory:

function onOpen (evt) {writeToScreen ("CONNECTED"); } funkcia onClose (evt) {writeToScreen ("DISCONNECTED"); } funkcia onError (evt) {writeToScreen ("ERROR:" + JSON.stringify (evt)); } funkcia onMessage (evt) {var dostalData = JSON.parse (evt.data); appendMessageToView ("Server", receiveData.body); }

Potom, aby sme predstavili výstup, použijeme funkcie appendMessageToView a writeToScreen:

funkcia appendMessageToView (nadpis, správa) {$ ("# messageContent"). append ("

"+ title +": "+ správa +"

");} funkcia writeToScreen (správa) {console.log (" Nová správa: ", správa);}

4.4. Spustenie a testovanie aplikácie

Sme pripravení aplikáciu otestovať, tak ju spustime:

spustiť cd websockets sbt

Keď je aplikácia spustená, môžeme so serverom chatovať na adrese // localhost: 9000:

Zakaždým, keď napíšeme správu, klikneme na tlačidlo Pošli server okamžite odpovie niektorými lorem ipsum zo služby JSON Placeholder.

5. Priama manipulácia s webovými zásuvkami pomocou streamov Akka

Ak spracovávame prúd udalostí zo zdroja a odosielame ich klientovi, môžeme to modelovať okolo prúdov Akka.

Pozrime sa, ako môžeme použiť prúdy Akka v príklade, keď server odosiela správy každé dve sekundy.

Začneme akciou WebSocket v HomeController:

public WebSocket akkaStreamsSocket () {return WebSocket.Json.accept (request -> {Sink in = Sink.foreach (System.out :: println); MessageDTO messageDTO = new MessageDTO ("1", "1", "Title", "Testovacie telo"); Zdroj out = Source.tick (Duration.ofSeconds (2), Duration.ofSeconds (2), MessageConverter.messageToJsonNode (messageDTO)); return Flow.fromSinkAndSource (in, out);}); }

The Zdroj č.zatrhnúť metóda má tri parametre. Prvým je počiatočné oneskorenie pred spracovaním prvého kliešťa a druhým je interval medzi po sebe nasledujúcimi kliešťami. Vo vyššie uvedenom úryvku sme nastavili obe hodnoty na dve sekundy. Tretím parametrom je objekt, ktorý by sa mal vrátiť pri každom začiarknutí.

Aby sme to videli v akcii, musíme upraviť URL v index akcie a smerovať k akkaStreamsSocket koncový bod:

Reťazec url = trasy.HomeController.akkaStreamsSocket (). WebSocketURL (požiadavka);

A teraz obnovujeme stránku, každé dve sekundy sa nám zobrazí nová položka:

6. Ukončenie herca

V určitom okamihu budeme musieť chat ukončiť, a to buď prostredníctvom žiadosti používateľa, alebo prostredníctvom časového limitu.

6.1. Ukončenie herectva

Ako zistíme, kedy bola sieť WebSocket zatvorená?

Play ukončí WebSocket automaticky, keď sa ukončí herec, ktorý manipuluje s WebSocket. Tento scenár teda môžeme vyriešiť implementáciou Herec # postStop metóda:

@Override public void postStop () vyvolá výnimku {log.info ("herec Messenger sa zastavil na {}", OffsetDateTime.now () .format (DateTimeFormatter.ISO_OFFSET_DATE_TIME)); }

6.2. Ručné ukončenie herca

Ďalej, ak musíme herca zastaviť, môžeme poslať a PoisonPill k hercovi. V našej príkladnej aplikácii by sme mali byť schopní vybaviť žiadosť o zastavenie.

Pozrime sa, ako to urobiť v onSendMessage metóda:

private void onSendMessage (JsonNode jsonNode) {RequestDTO requestDTO = MessageConverter.jsonNodeToRequest (jsonNode); Reťazcová správa = requestDTO.getMessage (). ToLowerCase (); if ("stop" .equals (message)) {MessageDTO messageDTO = createMessageDTO ("1", "1", "Stop", "zastavujúci aktér"); out.tell (MessageConverter.messageToJsonNode (messageDTO), getSelf ()); self (). tell (PoisonPill.getInstance (), getSelf ()); } else {log.info ("Herec prijal. {}", requestDTO); processMessage (requestDTO); }}

Keď dostaneme správu, skontrolujeme, či ide o požiadavku na zastavenie. Ak je, pošleme PoisonPill. V opačnom prípade žiadosť spracujeme.

7. Možnosti konfigurácie

Môžeme nakonfigurovať niekoľko možností, pokiaľ ide o to, ako by sa malo s WebSocket zaobchádzať. Pozrime sa na niekoľko.

7.1. Dĺžka rámu WebSocket

Komunikácia WebSocket zahŕňa výmenu dátových rámcov.

Dĺžka rámu WebSocket je konfigurovateľná. Máme možnosť prispôsobiť dĺžku rámu našim požiadavkám na aplikáciu.

Konfigurácia kratšej dĺžky rámca môže pomôcť znížiť útoky odmietnutia služby, ktoré používajú dlhé dátové rámce. Dĺžku rámu pre aplikáciu môžeme zmeniť zadaním maximálnej dĺžky v application.conf:

play.server.websocket.frame.maxLength = 64 kB

Túto možnosť konfigurácie môžeme tiež nastaviť zadaním maximálnej dĺžky ako parametra príkazového riadku:

sbt -Dwebsocket.frame.maxLength = 64 kB beh

7.2. Časový limit nečinnosti pripojenia

Predvolene je herec, ktorý používame na prácu s WebSocketom, ukončený po jednej minúte. Je to spôsobené tým, že server Play, na ktorom je spustená naša aplikácia, má predvolený časový limit nečinnosti 60 sekúnd. To znamená, že všetky spojenia, ktoré nedostanú žiadosť za šesťdesiat sekúnd, sa automaticky uzavrú.

Môžeme to zmeniť prostredníctvom možností konfigurácie. Poďme k našej application.conf a zmeňte server tak, aby nemal žiadny časový limit nečinnosti:

play.server.http.idleTimeout = "nekonečný"

Alebo môžeme zadať túto voľbu ako argumenty príkazového riadku:

sbt -Dhttp.idleTimeout = nekonečný beh

Môžeme to tiež nakonfigurovať zadaním devSettings v build.sbt.

Možnosti konfigurácie uvedené v build.sbt sa používajú iba vo vývoji, budú vo výrobe ignorované:

PlayKeys.devSettings + = "play.server.http.idleTimeout" -> "nekonečný"

Ak aplikáciu znovu spustíme, herec sa neukončí.

Hodnotu môžeme zmeniť na sekundy:

PlayKeys.devSettings + = "play.server.http.idleTimeout" -> "120 s"

Viac informácií o dostupných možnostiach konfigurácie sa dozvieme v dokumentácii Play Framework.

8. Záver

V tomto tutoriáli sme implementovali WebSockets do rámca Play s hercami Akka a prúdmi Akka.

Potom sme sa pozreli na to, ako priamo používať hercov Akka, a potom sme videli, ako je možné nastaviť prúdy Akka na spracovanie pripojenia WebSocket.

Na strane klienta sme na spracovanie našich udalostí WebSocket použili JavaScript.

Nakoniec sme sa pozreli na niektoré možnosti konfigurácie, ktoré môžeme použiť.

Ako obvykle je zdrojový kód tohto tutoriálu k dispozícii na GitHub.


$config[zx-auto] not found$config[zx-overlay] not found