Quarkus: rakenduste moderniseerimine helloworld näite põhjal JBoss EAP Quickstartist

Tere kõigile selles blogis, ja teie ees on neljas postitus Quarkusest!

Quarkus: rakenduste moderniseerimine helloworld näite põhjal JBoss EAP Quickstartist

Eelmine postitus käsitles seda, kuidas Quarkus ühendab MicroProfile'i ja Springi. Meenutame, et Quarkus positsioneeritakse kui "ülikiire subaatomaarne Java", tuntud ka kui "Kubernetes'i orienteeritud Java-stakk, mis on optimeeritud GraalVM ja OpenJDK HotSpot'i jaoks ning ehitatud parimatest raamatukogudest ja standarditest." Täna näitame, kuidas moderniseerida juba olemasolevaid Java-rakendusi, kasutades Quarkuse võimalusi, toome näitena helloworld rakendust Red Hat JBoss Enterprise Application Platform (JBoss EAP) Quickstart'ist, kus kasutatakse Quarkuses toetatud tehnoloogiaid CDI ja Servlet 3.

Siin on oluline märkida, et nii Quarkus kui ka JBoss EAP rõhutavad, et kasutada tööriistu, mis on maksimaalselt ehitatud põhinevates standarditel. Teil pole rakendust, mis töötaks JBoss EAP-l? Pole probleemi, selle saab hõlpsasti üle kanda olemasolevalt rakenduste serverilt JBoss EAP-le kasutades Red Hat Application Migration Toolkit. Pärast seda on moderniseeritud koodi lõplik ja töötav versioon saadaval hoidlas github.com/mrizzi/jboss-eap-quickstarts/tree/quarkus, moodulis helloworld.

Selle postituse kirjutamiseks kasutati Quarkuse juhendeid, peamiselt Creating Your First Application ja Building a Native Executable.

Saame koodi

Esiteks loome kohaliku klooni hoidlast JBoss EAP quickstarts:

$ git clone https://github.com/jboss-developer/jboss-eap-quickstarts.git
Kloonitakse 'jboss-eap-quickstarts'...
remote: Enumerating objects: 148133, done.
remote: Total 148133 (delta 0), reused 0 (delta 0), pack-reused 148133
Receiving objects: 100% (148133/148133), 59.90 MiB | 7.62 MiB/s, done.
Resolving deltas: 100% (66476/66476), done.
$ cd jboss-eap-quickstarts/helloworld/

Vaata, kuidas algne helloworld töötab

Sisuliselt on selle rakenduse olemus selge juba nimest, kuid me moderniseerime selle koodi rangelt teaduslikult. Seega alustame rakenduse vaatamist selle algses vormis.

Käivitame helloworld

1. Avame terminali ja liigume JBoss EAP'i juurkausta (selle saab alla laadida siit), st EAP_HOME kausta.

2. Käivitame JBoss EAP serveri vaikimisi profiiliga:

$ EAP_HOME/bin/standalone.sh

Märkus: Windowsis kasutatakse käivitamiseks skripti EAP_HOMEbinstandalone.bat.

Mõne sekundi pärast peaks logis ilmuma umbes järgmine:

[org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.2.0.GA (WildFly Core 6.0.11.Final-redhat-00001) käivitati 3315ms - Käivitati 306 527 teenusest (321 teenust on laisk, passiivne või nõudmisel)

3. Avame brauseris 127.0.0.1:8080 ja näeme järgmist:

Quarkus: rakenduste moderniseerimine helloworld näite põhjal JBoss EAP Quickstartist

Joonis 1. JBoss EAP kodu leht.

4. Järgime juhiseid juhendis Build and Deploy the Quickstart: käivitame helloworld ja täidame (projekti juurkaustast) järgmise käsu:

$ mvn clean install wildfly:deploy

Pärast selle käsu eduka täitmist näeme logis umbes järgmist:

[INFO] ------------------------------------------------------------------------ 
[INFO] BUILD SUCCESS 
[INFO] ------------------------------------------------------------------------ 
[INFO] Total time: 8.224 s

Nii et helloworld rakenduse esmakordne käitamine JBoss EAP-l võttis veidi üle 8 sekundi.

Testime helloworld

Toimides rangelt juhendi järgi Access the Application, avame brauseris 127.0.0.1:8080/helloworld ja näeme järgmist:

Quarkus: rakenduste moderniseerimine helloworld näite põhjal JBoss EAP Quickstartist

Joonis 2. Algne Hello World JBoss EAP-st.

Teeme muudatusi

Muudame sisendparameetri createHelloMessage(String name) World'ist Marco'ks:

writer.println("<h1>" + helloService.createHelloMessage("Marco") + "</h1>");

Taaskord täidame järgmise käsu:

$ mvn clean install wildfly:deploy

Seejärel värskendame brauseri lehte ja näeme, et tekst on muutunud:

Quarkus: rakenduste moderniseerimine helloworld näite põhjal JBoss EAP Quickstartist

Joonis 3. Hello Marco JBoss EAP-s.

Tühistame helloworld'i käivitamise ja lõpetame JBoss EAP töö

See ei ole vajalik, kuid kui soovite käivitamist tühistada, saab seda teha järgnevate käskudega:

$ mvn clean install wildfly:undeploy

Et lõpetada JBoss EAP eksemplari töö, lihtsalt vajutage Ctrl+C terminali aknas.

Moderniseerime helloworld'i

Nüüd keskendume algse helloworld rakenduse moderniseerimisele.

Loome uue haru

Loome uue tööharu pärast kiire algatuse projekti lõppemist:

$ git checkout -b quarkus 7.2.0.GA

Muudame faili pom.xml

Alustame rakenduse muutmist failist pom.xml. Et Quarkus saaks sinna XML-bloke lisada, täidame järgmise käsu helloworld kaustas:

$ mvn io.quarkus:quarkus-maven-plugin:0.23.2:create

Selle artikli kirjutamisel kasutati versiooni 0.23.2. Quarkusel ilmub sageli uusi versioone, et teada saada, milline on uusim versioon, vaadake veebilehte github.com/quarkusio/quarkus/releases/latest.

Eelmainitud käsk lisab pom.xml järgmised elemendid:

  • Üksus , mis määrab kasutatava Quarkuse versiooni.
  • Blokk , et importida Quarkus BOM (materjalide arve), et mitte iga Quarkuse sõltuvuse jaoks versiooni lisada.
  • Plugin quarkus-maven-plugin, mis vastutab rakenduse pakkimise ja arenduse režiimi esitamise eest.
  • Native profiil rakenduse käivitavate failide loomiseks.

Lisaks selle muudatuse manuaalne lisamine pom.xml:

  1. Võtame sildi välja blokist ja asetame selle sildi kohale. Kuna järgmisel sammul eemaldame ploki , tuleks säilitada.
  2. Eemaldame ploki , kuna Quarkusega töötades ei vaja see rakendus enam JBoss'i vanemat pom'i.
  3. Lisame sildi ja paigutame selle sildi alla. Versiooninumbri saate määrata enam kui soovite.
  4. Kustutame sildi , kuna see rakendus ei ole enam WAR, vaid tavaline JAR.
  5. Muudame järgmisi sõltuvusi:
    1. Vahetame sõltuvuse javax.enterprise:cdi-api io.quarkus:quarkus-arc vastu, eemaldades provided, kuna (dokumendi kohaselt) see Quarkus laiendus pakub CDI sõltuvuste süstimist.
    2. Vahetame sõltuvuse org.jboss.spec.javax.servlet:jboss-servlet-api_4.0_spec io.quarkus:quarkus-undertow vastu, eemaldades provided, kuna (dokumendi kohaselt) see Quarkus laiendus toetab servlet'e.
    3. Kustutame sõltuvuse org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec, kuna see on kaasas sõltuvustega, mida me just muutisime.

Pom.xml faili versioon koos kõigi muudatustega asub aadressil github.com/mrizzi/jboss-eap-quickstarts/blob/quarkus/helloworld/pom.xml.

Pange tähele, et ülaltoodud käsk mvn io.quarkus:quarkus-maven-plugin:0.23.2:create mitte ainult ei muuda pom.xml faili, vaid lisab projekti mitmeid komponente, nimelt järgmised failid ja kaustad:

  • Fail mvnw ja mvnw.cmd ning kaust .mvn: Maven Wrapper võimaldab käivitada Maven projekte kindla Maven versiooniga ilma selle versiooni installimiseta.
  • Kaust docker (kaustas src/main/): siin on näidised Dockerfile failide kohta native ja jvm režiimide jaoks (koos .dockerignore failiga).
  • Kaust resources (kaustas src/main/): siin asub tühi fail application.properties ja näidis Quarkuse index.html avalehe kohta (lisateave vaata siin: Run the modernized helloworld).

Käivitame helloworld
Kuna rakenduse testimiseks kasutame quarkus:dev, mis käivitab Quarkuse arenduse režiimis (lisateave leiate sellest jaost käesolevas juhendis) Development Mode).

Märkus: see samm toob ilmselt kaasa vea, kuna me pole teinud kõiki vajalikke muudatusi.

Nüüd käivitame käsu, et kontrollida, kuidas see töötab:

$ ./mvnw compile quarkus:dev
[INFO] Otsitakse projekte...
[INFO]
[INFO] --------------------------------
[INFO] Ehitatakse Quickstart: helloworld quarkus
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ helloworld ---
[INFO] Kasutatakse 'UTF-8' kodeeringut filtreeritud ressursside kopeerimiseks.
[INFO] Kopeerimine 2 ressurssi
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ helloworld ---
[INFO] Pole midagi kompileerida - kõik klassid on ajakohased
[INFO]
[INFO] --- quarkus-maven-plugin:0.23.2:dev (default-cli) @ helloworld ---
Kuulamine transport dt_socket aadressil: 5005
INFO  [io.qua.dep.QuarkusAugmentor] Algab quarkuse täiendamine
INFO  [org.jbo.threads] JBoss Threadsi versioon 3.0.0.Final
ERROR [io.qua.dev.DevModeMain] quarkuse käivitamine ebaõnnestus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Ehitus ebaõnnestus: ehitus ebaõnnestus, kuna vigu
	[viga]: Ehitus samm io.quarkus.arc.deployment.ArcProcessor#validate viskas erandi: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Rahuldamata sõltuvus tüübi org.jboss.as.quickstarts.helloworld.HelloService ja qualifierite [@Default] jaoks
	- java liige: org.jboss.as.quickstarts.helloworld.HelloWorldServlet#helloService
	- määratud CLASS bean [tüübid=[javax.servlet.ServletConfig, java.io.Serializable, org.jboss.as.quickstarts.helloworld.HelloWorldServlet, javax.servlet.GenericServlet, javax.servlet.Servlet, java.lang.Object, javax.servlet.http.HttpServlet], qualifierid=[@Default, @Any], sihtkoht=org.jboss.as.quickstarts.helloworld.HelloWorldServlet]
	at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:841)
	at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:214)
	at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:106)
	at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:249)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at io.quarkus.deployment.ExtensionLoader$1.execute(ExtensionLoader.java:780)
	at io.quarkus.builder.BuildContext.run(BuildContext.java:415)
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1426)
	at java.lang.Thread.run(Thread.java:748)
	at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Põhjus: javax.enterprise.inject.UnsatisfiedResolutionException: Rahuldamata sõltuvus tüübi org.jboss.as.quickstarts.helloworld.HelloService ja qualifierite [@Default] jaoks
	- java liige: org.jboss.as.quickstarts.helloworld.HelloWorldServlet#helloService
	- määratud CLASS bean [tüübid=[javax.servlet.ServletConfig, java.io.Serializable, org.jboss.as.quickstarts.helloworld.HelloWorldServlet, javax.servlet.GenericServlet, javax.servlet.Servlet, java.lang.Object, javax.servlet.http.HttpServlet], qualifierid=[@Default, @Any], sihtkoht=org.jboss.as.quickstarts.helloworld.HelloWorldServlet]
	at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:428)
	at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:371)
	at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:206)
	... 14 rohkem

Nii, see ei tööta… Miks?

Kõrvaleheitmine UnsatisfiedResolutionException viitab klassile HelloService, mis on klassi HelloWorldServlet (java liige: org.jboss.as.quickstarts.helloworld.HelloWorldServlet#helloService) liige. Probleem seisneb selles, et HelloWorldServlet vajab süstitud eksitenterit HelloService, kuid seda ei suudeta leida (kuigi need kaks klassi asuvad samas paketis).

On aeg naasta dokumentatsioonis ja lugeda, kuidas Quarkuses töötab Inject, seega Contexts and Dependency Injection (CDI). Seega avame Contexts and Dependency Injection juhendi ja jaoskonnas Bean Discovery loeme: "Bean klass, millel ei ole määravat bean annotatsiooni, ei otsita."

Küsimus klassist HelloService – selles tõepoolest sellist annotatsiooni ei ole. Seega tuleb see lisada, et Quarkus saaks bean'i otsida ja leida. Ja kuna see on stateless-objekt, saame julgelt lisada annotatsiooni @ApplicationScoped järgmiselt:

@ApplicationScoped
public class HelloService {

Märkus: Siin võib arenduskeskkond paluda teil lisada nõutud paketi (vt allolevat rida), ja selle tuleb teha käsitsi, nagu allpool:

import javax.enterprise.context.ApplicationScoped;

Kui te ei ole kindel, millist scope'i tuleks kasutada, kui algsele bean'ile ei ole see üldse määratud, uurige dokumentatsiooni JSR 365: Java kontekstid ja sõltuvuste süstimine 2.0—eelneva ulatuse standard.

Nüüd proovime uuesti rakendust käivitada käsuga ./mvnw compile quarkus:dev:

$ ./mvnw compile quarkus:dev
[INFO] Otsitakse projekte...
[INFO]
[INFO] --------------------------------
[INFO] Koostatakse Quickstart: helloworld quarkus
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ helloworld ---
[INFO] Kasutatakse 'UTF-8' kodeeringut filtreeritud ressursside kopeerimiseks.
[INFO] Kopeerimine 2 ressurssi
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ helloworld ---
[INFO] Muudatused tuvastatud - module taascompigeeritakse!
[INFO] Koostamisel 2 allika faili kaustas /home/mrizzi/git/forked/jboss-eap-quickstarts/helloworld/target/classes
[INFO]
[INFO] --- quarkus-maven-plugin:0.23.2:dev (default-cli) @ helloworld ---
Kuulame transporti dt_socket aadressil: 5005
INFO  [io.qua.dep.QuarkusAugmentor] (main) Algab quarkus'i suurendamine
INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus'i suurendamine lõpetatud 576ms jooksul
INFO  [io.quarkus] (main) Quarkus 0.23.2 käivitatud 1.083s jooksul. Kuulame aadressil: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profiil dev aktiveeritud. Otse kodeerimine aktiveeritud.
INFO  [io.quarkus] (main) Paigaldatud funktsioonid: [cdi]

Nüüd läheb kõik probleemideta läbi.

Käivitame täiustatud helloworld
Nagu logis on kirjutatud, avame brauseris 0.0.0.0:8080 (Quarkuse vaikimisi avaleht) ja näeme järgmist:

Quarkus: rakenduste moderniseerimine helloworld näite põhjal JBoss EAP Quickstartist

Kujutis 4. Quarkus dev avaleht.

Antud rakenduse WebServlet annotatsioonis on määratud järgmine konteksti määratlemine:

@WebServlet("/HelloWorld")
public class HelloWorldServlet extends HttpServlet {

Seega, läheme brauseris aadressile 0.0.0.0:8080/HelloWorld ja näeme järgmist:

Quarkus: rakenduste moderniseerimine helloworld näite põhjal JBoss EAP Quickstartist

Kujutis 5: The Quarkus dev leht Hello World rakenduse jaoks.

Noh, kõik töötab.

Ja nüüd teeme koodis muudatusi. Pange tähele, et käsk ./mvnw compile quarkus:dev töötab endiselt ja me ei kavatse seda peatada. Proovime nüüd teha samu – very trivial – muudatusi koodis ja vaatame, kuidas Quarkus arendaja elu lihtsamaks teeb:

writer.println("<h1>" + helloService.createHelloMessage("Marco") + "</h1>");

Salvestame faili ja seejärel värskendame veebilehte, et näha kirjutist Hello Marco, nagu on näidatud alloleval ekraanipildil:

Quarkus: rakenduste moderniseerimine helloworld näite põhjal JBoss EAP Quickstartist

Kujutis 6. Leht Hello Marco Quarkus dev-s.

Nüüd kontrollime väljundit terminalis:

INFO  [io.qua.dev] (vert.x-worker-thread-3) Muudatused tuvastatud, koostatakse [/home/mrizzi/git/forked/jboss-eap-quickstarts/helloworld/src/main/java/org/jboss/as/quickstarts/helloworld/HelloWorldServlet.java]
INFO  [io.quarkus] (vert.x-worker-thread-3) Quarkus peatati 0.003s jooksul
INFO  [io.qua.dep.QuarkusAugmentor] (vert.x-worker-thread-3) Algab quarkus'i suurendamine
INFO  [io.qua.dep.QuarkusAugmentor] (vert.x-worker-thread-3) Quarkus'i suurendamine lõpetatud 232ms jooksul
INFO  [io.quarkus] (vert.x-worker-thread-3) Quarkus 0.23.2 käivitatud 0.257s jooksul. Kuulame aadressil: http://0.0.0.0:8080
INFO  [io.quarkus] (vert.x-worker-thread-3) Profiil dev aktiveeritud. Otse kodeerimine aktiveeritud.
INFO  [io.quarkus] (vert.x-worker-thread-3) Paigaldatud funktsioonid: [cdi]
INFO  [io.qua.dev] (vert.x-worker-thread-3) Kuum asendamise koguaeg: 0.371s

Lehe värskendamine käivitas allika koodimuudatuste tuvastamise, ja Quarkus täitis automaatse 'stop-start' protseduuri. Ja see kõik toimus vaid 0.371 sekundi jooksul (see on see 'ülimalt kiire subatomaarne Java').

Käivitame helloworld JAR paketina
Nüüd, kui kood töötab nii nagu peab, pakime selle järgmise käsuga:

$ ./mvnw clean package

See käsk loob kaks JAR-faili kaustas /target: fail helloworld-.jar, mis on standardne artefakt, mille Maven-käsk koos klasside ja projekti ressurssidega loob. Ja fail helloworld—runner.jar, mis on käivitatav JAR.

Pange tähele, et see ei ole uber-jar, kuna kõik sõltuvused on lihtsalt kopeeritud kausta /target/lib (mitte pakitud JAR-faili). Seetõttu, et käivitada seda JAR-faili ühest kaustast või teisel hostil, tuleb sinna kopeerida nii JAR-fail kui ka kaust /lib, arvestades, et MANIFEST.MF failis pakendis JAR on Class-Path elemendi selge loetelu kaustas lib.
Kuidas luua uber-jar rakendusi saate lugeda juhendist Uber-Jar Creation.

Käivitame helloworld, mis on pakendatud JAR-failina

Nüüd saab meie JAR-i käivitada tavalise java käsuga:

$ java -jar ./target/helloworld-<version>-runner.jar
INFO  [io.quarkus] (main) Quarkus 0.23.2 käivitatud 0.673s jooksul. Kuulame aadressil: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profiil prod aktiveeritud.
INFO  [io.quarkus] (main) Paigaldatud funktsioonid: [cdi]

Pärast seda, kui kõik see on tehtud, avage brauseris 0.0.0.0:8080 ja kontrollige, et kõik töötab nagu peab.

Kogume helloworld natiivseks käivitatavaks failiks

Seega töötab meie helloworld iseseisva Java rakendusena, kasutades Quarkus'e sõltuvusi. Kuid saab minna kaugemale ja muuta selle natiivseks käivitatavaks failiks.

Installeerime GraalVM
Esiteks tuleb selleks installida vajalikud tööriistad:

1. Laadige alla GraalVM 19.2.0.1 aadressilt github.com/oracle/graal/releases/tag/vm-19.2.0.1.

2. Lahtipakkige allalaaditud arhiiv:

$ tar xvzf graalvm-ce-linux-amd64-19.2.0.1.tar.gz

3. Liigume kausta untar.

4. Käivitame allpool toodud käsu, et alla laadida ja lisada natiivne pilt:

$ ./bin/gu install native-image

5. Määrame kausta, mis loodi 2. sammul, keskkonnamuutujasse GRAALVM_HOME:

$ export GRAALVM_HOME={untar-folder}/graalvm-ce-19.2.0.1)

Lisainfot ja juhiseid muudele operatsioonisüsteemidele leiate juhendist Building a Native Executable—Prerequisites.

Kompileerime helloworld natiivseks käivitatavaks failiks
Loe juhendit Building a Native Executable—Producing a native executable: „Nüüd loome natiivse käivitatava faili meie rakenduse jaoks, et vähendada selle käivitusaega ja kettaruumi. Käivitatav fail sisaldab kõike, mis on vajalik rakenduse käivitamiseks, sealhulgas JVM-i (teisisõnu, selle kärbitud versiooni, mis sisaldab vaid seda, mis vajalik rakenduse käitmiseks) ja meie rakendust ise.”

Natiivse käivitatava faili loomiseks tuleb aktiveerida Maven'i native-profiil:

$ ./mvnw package -Pnative

Meie kompilatsioon kestis ühe minuti ja 10 sekundit ning lõppfail helloworld—runner f loodi kaustas /target.

Käivitame natiivse käivitatava faili helloworld

Eelmisel sammul saime käivitatava faili /target/helloworld—runner. Nüüd käivitame selle:

$ ./target/helloworld-<version>-runner
INFO  [io.quarkus] (main) Quarkus 0.23.2 käivitati 0.006s. Kuulab: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profiil prod aktiveeritud.
INFO  [io.quarkus] (main) Installitud omadused: [cdi]

Avame taas brauseri 0.0.0.0:8080 ja kontrollime, et kõik töötab nagu peab.

Jätkub!

Usume, et selles postituses käsitletud (kuigi lihtsas näites) Java rakenduste moderniseerimise meetod, kasutades Quarkuse võimalusi, väärib aktiivset rakendamist reaalses elus. Tõenäoliselt kohtate teatud probleeme, mille lahendamist käsitleme osaliselt järgmises postituses, kus räägime mälukasutuse mõõtmisest, et hinnata jõudluse paranemist, mis on oluline osa kogu rakenduste moderniseerimise protsessist.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster