WebSockets a Play keretrendszerrel és az Akkával

1. Áttekintés

Amikor webes ügyfeleinknek párbeszédet kívánunk fenntartani szerverünkkel, akkor a WebSockets hasznos megoldás lehet. A WebSockets tartós full-duplex kapcsolatot tart fenn. Ez lehetőséget ad arra, hogy kétirányú üzeneteket küldjünk a szerver és az ügyfél között.

Ebben az oktatóanyagban megtanuljuk, hogyan kell használni a WebSocketeket az Akkával a Play keretrendszerben.

2. Beállítás

Hozzunk létre egy egyszerű csevegőalkalmazást. A felhasználó üzeneteket küld a kiszolgálónak, a szerver pedig a JSONPlaceholder üzenettel válaszol.

2.1. A Play Framework alkalmazás beállítása

Ezt az alkalmazást a Play Framework segítségével építjük fel.

Kövessük a Bevezetés a játékba a Java-ban utasításokat egy egyszerű Play Framework alkalmazás beállításához és futtatásához.

2.2. A szükséges JavaScript fájlok hozzáadása

Ezenkívül a kliensoldali szkriptek készítéséhez a JavaScript-szel is együtt kell dolgoznunk. Ez lehetővé teszi számunkra, hogy új üzeneteket fogadjunk a szerverről. Ehhez a jQuery könyvtárat fogjuk használni.

Tegyük hozzá a jQuery-t a alkalmazás / nézetek / index.scala.html fájl:

2.3. Akka beállítása

Végül az Akkával fogjuk kezelni a WebSocket kapcsolatokat a szerver oldalon.

Navigáljunk a build.sbt fájlt, és adja hozzá a függőségeket.

Hozzá kell adnunk a akka-színész és akka-testkit függőségek:

libraryDependencies + = "com.typesafe.akka" %% "akka-színész"% akkaVersion libraryDependencies + = "com.typesafe.akka" %% "akka-testkit"% akkaVersion

Szükségünk van ezekre, hogy használhassuk és tesztelhessük az Akka Framework kódot.

Ezután az Akka-patakokat fogjuk használni. Tehát tegyük hozzá a akka-patak függőség:

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

Végül meg kell hívnunk egy pihenési végpontot egy Akka színésztől. Ehhez szükségünk lesz a akka-http függőség. Amikor ezt megtesszük, a végpont visszaadja a JSON-adatokat, amelyeket deszerializálnunk kell, ezért hozzá kell adnunk a akka-http-jackson függőség is:

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

És most minden készen áll. Lássuk, hogyan lehet működtetni a WebSocketeket!

3. WebSocket kezelése Akka Actors-szal

A Play WebSocket kezelési mechanizmusa az Akka folyamok köré épül. A WebSocket Flow-ként van modellezve. Tehát a bejövő WebSocket üzenetek bekerülnek a folyamatba, és a folyamat által létrehozott üzenetek kiküldésre kerülnek az ügyfélnek.

A WebSocket Actor használatával történő kezeléséhez szükségünk lesz a Play segédprogramra ActorFlow amely átalakítja egy ActorRef áramláshoz. Ehhez főleg kis Java-kódra van szükség, kis konfigurációval.

3.1. A WebSocket vezérlő módszer

Először is szükségünk van a Materializer példa. A Materializer gyár a stream végrehajtó motorok számára.

Be kell adnunk a ActorSystem és a Materializer a vezérlőbe app / controllers / HomeController.java:

privát ActorSystem activSystem; magán Materializer materializáló; @ Inject public HomeController (ActorSystem activSystem, Materializer materializer) {this.actorSystem = actorSystem; this.materializer = materializer; }

Adjunk hozzá egy socket vezérlő módszert:

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

Itt hívjuk a függvényt acceptOrResult amely elviszi a kérés fejlécét, és egy jövõt ad vissza. A visszatérő jövő a WebSocket üzenetek kezelésének folyamata.

Ehelyett elutasíthatjuk a kérést, és visszaküldhetünk egy elutasítási eredményt.

Most hozzuk létre a folyamatot:

privát CompletionStage<>> createActorFlow (Http.RequestHeader kérés) {return CompletableFuture.completedFuture (F.Either.Right (createFlowForActor ())); }

A F osztály a Play keretrendszerben meghatározza a funkcionális programozási stílus segédeszközeit. Ebben az esetben használjuk F.Bármelyik is hogy elfogadja a kapcsolatot és visszaadja az áramlást.

Tegyük fel, hogy el akartuk utasítani a kapcsolatot, ha az ügyfél nincs hitelesítve.

Ehhez ellenőrizhetjük, hogy a munkamenetben be van-e állítva egy felhasználónév. Ha pedig nem, akkor visszautasítjuk a kapcsolatot a HTTP 403 Tiltott paranccsal:

privát CompletionStage<>> createActorFlow2 (Http.RequestHeader kérés) {return CompletableFuture.completedFuture (request.session () .getOptional ("felhasználónév") .map (felhasználónév -> F.MindenkiJobbra (createFlowForActor ())) .vagyElseGet (() -> F.Ejben.Bal (tiltott ()))); }

Használunk F.Vagy. Balra hogy ugyanúgy elutasítsuk a kapcsolatot, mint amellyel áramlást biztosítunk F. Bármelyik. Jobb.

Végül összekapcsoljuk a folyamatot az üzenetet kezelő szereplővel:

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

A ActorFlow.actorRef olyan áramlást hoz létre, amelyet a Hírnök színész.

3.2. A útvonalak File

Most tegyük hozzá a útvonalak definíciók a vezérlő metódusaira konf / útvonalak:

GET / controllers.HomeController.index (kérés: Request) GET / chat-vezérlők.HomeController.socket GET / chat / with / stream vezérlőkkel.HomeController.akkaStreamsSocket GET / assets / * fájl vezérlők.Assets.versioned (path = "/ public" , fájl: eszköz)

Ezek az útvonal-meghatározások a bejövő HTTP-kéréseket leképezik a vezérlő műveleti módszereire, amint azt a Java-alkalmazás útválasztása című részben ismertetjük.

3.3. A színész megvalósítása

A színészosztály legfontosabb része a createReceive módszer amely meghatározza, hogy a színész mely üzeneteket tudja kezelni:

@ Nyilvános felülbírálás A createReceive () {return ReceiveBuilder () .match (JsonNode.class, this :: onSendMessage) .matchAny (o -> log.error ("Fogadott ismeretlen üzenet: {}", o.getClass ())) .épít(); }

A színész továbbítja az összes üzenetet, amely megfelel a JsonNode osztály a onSendMessage kezelő módszer:

private void onSendMessage (JsonNode jsonNode) {RequestDTO requestDTO = MessageConverter.jsonNodeToRequest (jsonNode); Karakterlánc üzenet = requestDTO.getMessage (). ToLowerCase (); // .. processMessage (requestDTO); }

Ezután a kezelő minden üzenetre válaszol a processMessage módszer:

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

3.4. Rest API fogyasztása Akka HTTP-vel

HTTP kéréseket küldünk a próbabábuüzenet-generátornak a JSONPlaceholder Posts webhelyen. Amikor megérkezik a válasz, írásban elküldjük a választ az ügyfélnek ki.

Legyen egy módszerünk, amely véletlenszerű post-azonosítóval hívja meg a végpontot:

privát CompletionStage getRandomMessage () {int postId = ThreadLocalRandom.current (). nextInt (0, 100); térjen vissza a Http.get (getContext (). getSystem ()) .singleRequest (HttpRequest.create ("//jsonplaceholder.typicode.com/posts/" + postId)); }

Feldolgozzuk a HttpResponse hívjuk a szolgáltatást, hogy megkapjuk a JSON választ:

privát CompletionStage ConsumeHttpResponse (HttpResponse httpResponse) {Materializer materializer = Materializer.matFromSystem (getContext (). getSystem ()); return Jackson.unmarshaller (MessageDTO.class) .unmarshal (httpResponse.entity (), materializer) .thenApply (messageDTO -> {log.info ("Fogadott üzenet: {}", messageDTO); discardEntity (httpResponse, materializer); return messageDTO;}); }

A MessageConverter osztály az egyik közötti átalakításhoz használható segédprogram JsonNode és a DTO-k:

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

Ezután el kell vetnünk az entitást. A discardEntityBytes a kényelmi módszer azt a célt szolgálja, hogy az entitást könnyen eldobjuk, ha annak nincs célja számunkra.

Lássuk, hogyan dobhatjuk el a bájtokat:

private void discardEntity (HttpResponse httpResponse, Materializer materializer) {HttpMessage.DiscardedEntity discarded = httpResponse.discardEntityBytes (materializer); discarded.completionStage () .whenComplete ((kész, ex) -> log.info ("Az entitás teljesen eldobva!")); }

Miután elvégeztük a WebSocket kezelését, nézzük meg, hogyan állíthatunk be ehhez klienst a HTML5 WebSockets segítségével.

4. A WebSocket kliens beállítása

Készítsünk kliensünk számára egy egyszerű webalapú csevegőalkalmazást.

4.1. A vezérlő művelete

Meg kell határoznunk egy vezérlő műveletet, amely megjeleníti az index oldalt. Ezt a vezérlő osztályba tesszük app.controllers.HomeController:

public Result index (Http.Request kérés) {String url = route.HomeController.socket () .webSocketURL (request); return ok (nézetek.html.index.render (url)); } 

4.2. A sablon oldal

Most térjünk át a app / views / ndex.scala.html oldalt, és adjon hozzá egy tárolót a beérkezett üzenetekhez és egy űrlapot új üzenet rögzítéséhez:

 F Küldés 

Azt is meg kell adnunk a WebSocket vezérlő műveletének URL-jében, hogy ezt a paramétert deklaráljuk a app / views / index.scala.htmloldal:

@ (URL: karakterlánc)

4.3. WebSocket eseménykezelők JavaScript-ben

És most hozzáadhatjuk a JavaScript-et a WebSocket események kezeléséhez. Az egyszerűség kedvéért hozzáadjuk a JavaScript funkciókat az aljára app / views / index.scala.html oldalt.

Kijelentjük az eseménykezelőket:

var webSocket; var messageInput; függvény init () {initWebSocket (); } function initWebSocket () {webSocket = új WebSocket ("@ url"); webSocket.onopen = onOpen; webSocket.onclose = onClose; webSocket.onmessage = onMessage; webSocket.onerror = onError; }

Adjuk hozzá maguk a kezelők:

function onOpen (evt) {writeToScreen ("CSATLAKOZOTT"); } function onClose (evt) {writeToScreen ("BONTOTT"); } function onError (evt) {writeToScreen ("HIBA:" + JSON.stringify (evt)); } function onMessage (evt) {var kapottData = JSON.parse (evt.data); appendMessageToView ("Szerver", receivesData.body); }

Ezután a kimenet bemutatásához használjuk a függvényeket appendMessageToView és writeToScreen:

function appendMessageToView (cím, üzenet) {$ ("# messageContent"). append ("

"+ title +": "+ message +"

");} függvény writeToScreen (üzenet) {console.log (" Új üzenet: ", üzenet);}

4.4. Az alkalmazás futtatása és tesztelése

Készen állunk az alkalmazás tesztelésére, ezért futtassuk:

cd websockets sbt fut

Az alkalmazás futtatásával meglátogathatjuk a szervert // localhost: 9000:

Minden alkalommal, amikor beírunk egy üzenetet, és eltaláljuk Küld a szerver azonnal válaszol néhányukkal lorem ipsum a JSON Placeholder szolgáltatásból.

5. A WebSockets kezelése közvetlenül az Akka Stream szolgáltatással

Ha egy eseményforrást feldolgozunk egy forrásból, és elküldjük ezeket az ügyfélnek, akkor ezt modellezhetjük az Akka folyamok körül.

Nézzük meg, hogyan használhatjuk az Akka adatfolyamokat egy példában, amikor a szerver két másodpercenként küld üzeneteket.

A WebSocket művelettel kezdjük a HomeController:

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

A Forrás#ketyegés módszer három paramétert vesz fel. Az első az első kullancs feldolgozása előtti késleltetés, a második pedig az egymást követő kullancsok közötti intervallum. Mindkét értéket két másodpercre állítottuk be a fenti részletben. A harmadik paraméter egy olyan objektum, amelyet minden kullancsnál vissza kell adni.

Ahhoz, hogy ezt működés közben lássuk, módosítanunk kell az URL-t a index és tegye rá a akkaStreamsSocket végpont:

Karakterlánc URL = útvonalak.HomeController.akkaStreamsSocket (). WebSocketURL (kérés);

És most frissítve az oldalt, két másodpercenként új bejegyzést fogunk látni:

6. A színész megszüntetése

Valamikor le kell állítanunk a csevegést, akár felhasználói kéréssel, akár időkorlát segítségével.

6.1. A színész megszüntetésének kezelése

Hogyan észlelhetjük, ha a WebSocket bezárt?

A Play automatikusan bezárja a WebSocket-et, amikor a WebSocket-et kezelő színész megszűnik. Tehát kezelhetjük ezt a forgatókönyvet a Színész # postStop módszer:

@Orride public void postStop () dobja a (z) {log.info ("Messenger színész leállt {}" - nál, OffsetDateTime.now () .format (DateTimeFormatter.ISO_OFFSET_DATE_TIME) kivétel); }

6.2. A színész kézi megszüntetése

Továbbá, ha le kell állítanunk a színészt, küldhetünk egy Mérgező tabletta a színésznek. Példapéldánkban képesnek kell lennünk egy „stop” kérés kezelésére.

Nézzük meg, hogyan lehet ezt megtenni a onSendMessage módszer:

private void onSendMessage (JsonNode jsonNode) {RequestDTO requestDTO = MessageConverter.jsonNodeToRequest (jsonNode); Karakterlánc üzenet = requestDTO.getMessage (). ToLowerCase (); if ("stop" .egyenlő (üzenet)) {MessageDTO messageDTO = createMessageDTO ("1", "1", "Stop", "Leállító szereplő"); out.tell (MessageConverter.messageToJsonNode (messageDTO), getSelf ()); self (). tell (PoisonPill.getInstance (), getSelf ()); } else {log.info ("Színész megkapta. {}", requestDTO); processMessage (requestDTO); }}

Amikor üzenetet kapunk, ellenőrizzük, hogy ez leállítási kérelem-e. Ha igen, akkor elküldjük a Mérgező tabletta. Ellenkező esetben feldolgozzuk a kérést.

7. Konfigurációs beállítások

Számos lehetőséget konfigurálhatunk a WebSocket kezelésének szempontjából. Nézzünk meg néhányat.

7.1. WebSocket kerethossz

A WebSocket kommunikáció magában foglalja az adatkeretek cseréjét.

A WebSocket kerethossza konfigurálható. Lehetőségünk van arra, hogy a keret hosszát alkalmazási követelményeinkhez igazítsuk.

A rövidebb kerethossz konfigurálása csökkentheti a hosszú adatkereteket használó szolgáltatásmegtagadási támadásokat. Megváltoztathatjuk az alkalmazás kerethosszát a maximális hosszúság megadásával application.conf:

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

Ezt a konfigurációs beállítást úgy is beállíthatjuk, hogy a maximális hosszúságot parancssori paraméterként adja meg:

sbt -Dwebsocket.frame.maxLength = 64k futás

7.2. A kapcsolat üresjárati időkorlátja

Alapértelmezés szerint a WebSocket kezeléséhez használt szereplő egy perc múlva megszűnik. Ennek oka, hogy a Play kiszolgáló, amelyben az alkalmazásunk fut, alapértelmezett üresjárati időkorlátja 60 másodperc. Ez azt jelenti, hogy minden olyan kapcsolat, amely hatvan másodperc alatt nem kap kérelmet, automatikusan bezárul.

Ezt a konfigurációs lehetőségek segítségével változtathatjuk meg. Menjünk át a miénkre application.conf és módosítsa a szervert, hogy ne legyen tétlen időkorlátja:

play.server.http.idleTimeout = "végtelen"

Vagy átadhatjuk az opciót parancssori argumentumként:

sbt -Dhttp.idleTimeout = végtelen futás

Ezt a specifikációval is konfigurálhatjuk devSettings ban ben build.sbt.

A konfigurációs beállítások itt vannak megadva build.sbt csak fejlesztésben használják, a gyártás során figyelmen kívül hagyják:

PlayKeys.devSettings + = "play.server.http.idleTimeout" -> "végtelen"

Ha újra futtatjuk az alkalmazást, a színész nem szűnik meg.

Az értéket másodpercekre változtathatjuk:

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

Az elérhető konfigurációs lehetőségekről a Play Framework dokumentációjában tájékozódhat.

8. Következtetés

Ebben az oktatóanyagban a WebSocketeket a Play keretrendszerben valósítottuk meg Akka színészekkel és az Akka Streamekkel.

Ezután megvizsgáltuk az Akka színészek közvetlen használatát, majd láttuk, hogyan lehet az Akka Streameket beállítani a WebSocket kapcsolat kezelésére.

A kliens oldalon JavaScript-et használtunk a WebSocket eseményeink kezeléséhez.

Végül megvizsgáltunk néhány beállítási lehetőséget, amelyeket felhasználhatunk.

Szokás szerint az oktatóanyag forráskódja elérhető a GitHubon.


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