PDF-1 - Tomas Vitvar
Transcription
PDF-1 - Tomas Vitvar
MiddlewareandWebServices Lecture3:ApplicationProtocols doc.Ing.TomášVitvar,Ph.D. tomas@vitvar.com•@TomasVitvar•http://vitvar.com CzechTechnicalUniversityinPrague FacultyofInformationTechnologies•SoftwareandWebEngineering•http://vitvar.com/courses/mdw Modified:TueSep152015,19:58:18 Humlav0.3 Overview IntroductiontoApplicationProtocols ‒SynchronousandAsynchronousCommunication ‒SelectedNetworkingConcepts SimpleProtocolExample IntroductiontoHTTP Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒2‒ ApplicationProtocols Rememberthis AppprotocolsmostlyontopoftheTCPLayer ‒useTCPsocketforcommunication Majorprotocols ‒HTTP–mostoftheappprotocolslayeredonHTTP →widespread,but:implementorsoftenbreakHTTPsemantics ‒RMI–RemoteMethodInvocation →Java-specific,ratherinterface →mayuseHTTPunderneath(amongotherthings) ‒XML-RPC–RemoteProcedureCallandSOAP →Again,HTTPunderneath ‒WebSocket–newprotocolpartofHTML5 Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒3‒ Socket Handshaking(connectionestablishment) ‒Theserverlistensat[dst_ip,dsp_port] ‒Three-wayhandshake: →theclientat[src_ip,src_port]sendsaconnectionrequest →theserverresponds →theclientacknowledgestheresponse,cansenddataalong ‒Resultisasocket(virtualcommunicationchannel)withuniqueidentification: socket=[src_ip,src_port;dst_ip,dst_port] Datatransfer(resourceusage) ‒Client/serverwrites/readsdatato/fromthesocket ‒TCPfeatures:reliabledelivery,correctorderofpackets,flowcontrol Connectionclose Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒4‒ AddressinginApplicationProtocol IPaddressing:IPisanaddressofamachineinterface ‒Amachinecanhavemultipleinterfaces(eth0,eth1,bond0,...) TCPaddressing:TCPportisanaddressofanapprunningonamachine andlisteningonamachineinterface ‒MultipleapplicationswithdifferentTCPportsmaylistenonamachineinterface Applicationaddressing ‒Additionalmechanismstoaddressentitieswithinanapplication ‒TheyareoutofscopeofIP/TCP,theyareappspecific →forexample,WebappsservedbyasingleWebserver Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒5‒ VirtualIP VirtualIP ‒AdditionalIPaddressesassignedtoanetworkinterface →Forexample,eth0–eth0:1,eth0:2,eth0:3,... →AprocesscanbindtothevirtualIP →MultipleprocessescanlistenonthesametcpportbutondifferentvirtualIPs Benefits ‒FloatingIP–aprocesscanmovetransparentlytoanotherphysicalmachine ‒Networkconfigurationcanbepreserved,noneedtoreconfigure ‒FailoverconceptusesfloatingIPs Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒6‒ VirtualIPConfiguration StepstoconfigurevirtualIPinLinux(exampleforeth0) 1. Findouttheinterface'snetworkmask 1 2 3 4 $ifconfigeth0 eth0Linkencap:EthernetHWaddr00:0C:29:AB:5E:6A inetaddr:172.16.169.184Bcast:172.16.169.255Mask:255.255.255.0 ... 2. CreatevirtualIPusingifconfig ‒itshouldusethesamenetworkmask ‒itshouldbefree,usuallyallocatedtobeusedasavirtualIP 5 6 7 8 $sudoifconfigeth0:1172.16.169.184netmask255.255.255.0 $ifconfigeth0:1 eth0:1Linkencap:EthernetHWaddr00:0C:29:AB:5E:6A inetaddr:172.16.169.186Bcast:172.16.169.255Mask:255.255.255.0 3. Updateneighbours'ARP(AddressResolutionProtocol)caches ‒toassociatethevirtualIPwithMACaddressofeth0 ‒whenthevirtualIPwasinuseonothernodeorinterface 9 $sudoarping-q-U-c3-Ieht0172.16.169.184 Tasks ‒ConfigureavirtualIPonyourcomputerandtestitusingping Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒7‒ Overview IntroductiontoApplicationProtocols ‒SynchronousandAsynchronousCommunication ‒SelectedNetworkingConcepts SimpleProtocolExample IntroductiontoHTTP Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒8‒ SynchronousandAsynchronousCommunication Synchronous ‒onesocket,|treq–tres|issmall ‒easytoimplementanddeploy,onlystandardfirewallconfig ‒onlytheserverdefinesendpoint Asynchronous ‒request,responseeachhassocket,clientandserverdefineendpoints ‒|treq–tres|canbelarge(hours,evendays) ‒hardertodoacrossnetworkelements(private/publicnetworksissue) Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒9‒ AsynchronousviaIntermediary Intermediary ‒Acomponentthatdecouplesaclient-servercommunication ‒Itincreasesreliabilityandperformance →Theservermaynotbeavailablewhenaclientsendsarequest →Therecanbemultipleserversthatcanhandletherequest FurtherConcepts ‒MessageQueues(MQ)–queue-basedcommunication ‒Publish/Subscribe(P/S)–event-drivencommunication Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒10‒ AsynchronousviaPolling Polling–onlyclientsopensockets ‒Aclientperformsmultiplerequest-responseinteractions →Thefirstinteractioninitiatesaprocessontheserver →Subsequentinteractionscheckfortheprocessingstatus →Thelastinteractionretrievestheprocessingresult Propertiesofenvironments ‒Aservercannotopenasocketwiththeclient(networkrestrictions) ‒TypicallyontheWeb(aclientrunsinabrowser) Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒11‒ Overview IntroductiontoApplicationProtocols ‒SynchronousandAsynchronousCommunication ‒SelectedNetworkingConcepts SimpleProtocolExample IntroductiontoHTTP Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒12‒ Public/PrivateNetworkConfiguration Addscomplexitytoconfigurationofapplication ‒Configexampleatserverwitheth0=147.32.100.1(iptables) 1 2 3 4 5 6 #enableipforwardingfromoneinterfacetoanotherwithinlinuxcore echo1>/proc/sys/net/ipv4/ip_forward #redirectallcommunicationcomingtotcp/3000to192.168.1.2:4000 iptables-tnat-APREROUTING-ieth0-ptcp--dport3000-jDNAT\ --to-dest192.168.1.2--to-port4000 Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒13‒ DemilitarizedZones DMZ=DemilitarizedZone ‒subnetwithinanorganization'snetworkonapublicnetwork ‒specialcareofsecurityenforcedthroughinternalpolicies ‒Forexample: →noaccesstoalllivedata,subsetscopiedinbatches →frequentmonitoring Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒14‒ VirtualPrivateNetwork VPN=VirtualPrivateNetwork ‒anoverlaynetworkbetweenaclientandaserver ‒thenetworkspansaccrossunderlyingnetworkelements ‒Example: →VPNclientstartsaVPNconnectionwiththeVPNservervianetworkinterfaces →VPNserverassignsanIPaddresstotheVPNclientfromtheserver'ssubnet →PacketsinVPNcommunicationareencryptedandsentoutinanouterVPN packet,e.g.IPSecpacket Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒15‒ ProxyServer ProxyServer ‒Centralizedaccesscontrolbasedoncontent ‒Perfomsrequestonbehalfoftheclient →Cachescontenttoincreaseperformance,limitsnetworktraffic →Filtersrequestsbasedontheirdestinations ‒Widelyusedinprivatenetworksincompanies ‒MostoftheproxyserverstodayareWebproxyservers Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒16‒ ReverseProxyServer ReverseProxyServer ‒Aggregatesmultiplerequest-responseinteractionswithback-endsystems ‒Processestherequestonbehalfoftheclient ‒Providesadditionalvaluestocommunication →Datatransformations →Security–authentication,authorization →Orchestrationofcommunicationwithback-endsystems ‒Examples:EnterpriseServiceBus,SecurityGateway Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒17‒ Overview IntroductiontoApplicationProtocols SimpleProtocolExample IntroductiontoHTTP Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒18‒ TCPSocketProtocol ExamplesimpleTCPSocketprotocolinJava ‒functions(verbs):addandbye ‒datasyntax:add"^[0-9]+[0-9]+$",bye"^$"(regulargrammars) ‒datasemantics:adddecimalnumbers,byenone ‒process:transitionsS1—add—S1,S1—bye—S0,whereS0,S1arestatessuchthat S1=connectionestablished,S0=connectionclosed. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 packagecom.vitvar.ctu.mdw; importjava.io.*; importjava.net.*; importjava.util.regex.*; /** *Simpleprotocolexample.Theclassstartsalistenerontheport8080. *Whenaclientconnects,theserverparsestheinputinaform"addab", *where"a"and"b"areintegervalues,addsthetwonumbersandsends *theresultbacktotheclient.Thecommunicationendswhentheclientsends"bye". * *@authortomas@vitvar.com * */ publicclassSimpleProtocol{ publicstaticvoidmain(String[]args)throwsIOException{ //infomessagetotheconsole System.out.println("Listeningonport8080..."); Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒19‒ TCPSocketProtocol(Cont.) 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 //listenonport8080 ServerSocketserverSocket=newServerSocket(8080); SocketclientSocket=serverSocket.accept(); //createreaderandwritertoreadfromandwritetothesocket PrintWriterout=newPrintWriter(clientSocket.getOutputStream(),true); BufferedReaderin=newBufferedReader( newInputStreamReader(clientSocket.getInputStream())); //printinformationtotheclient out.println("verbs:addab,bye"); //grammardefinition Patternp=Pattern.compile("^add([0-9]+)([0-9]+)$"); Matcherm;Stringmessage; //readinputfromtheclientandprocesstheinput while((message=in.readLine())!=null){ if((m=p.matcher(message)).matches()) out.println("Result:"+(Integer.parseInt(m.group(1))+ Integer.parseInt(m.group(2)))); else if(message.equals("bye")){ out.println("Goodbye!"); break; }else out.println("Donotunderstand:"+message); } } } Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒20‒ Testing Manyappprotocolscommunicateinplaintext ‒messagesinASCIIorBase64encoded(printablecharsonly) ‒thisallowstotestthemjustwithTelnet →Telnetdoesnotknowaboutanyprotocol-specificsemantics →onlyopens,reads/writes,andclosesthesocket Testingourprotocol 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #1.runthelistener bin/simple_protocol.sh Listeningonport8080... #2.openthesocketusingtelnetbutfirstdigforDNSlookup telnet127.0.0.18080 Trying127.0.0.1... Connectedtolocalhost. Escapecharacteris'^]'. Verbs:addab,bye. add34 Theresultis:7 minus75 Donotunderstand:minus75 bye Goodbye! Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒21‒ Overview IntroductiontoApplicationProtocols SimpleProtocolExample IntroductiontoHTTP ‒StateManagement Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒22‒ HypertextTransferProtocol–HTTP Applicationprotocol,basisofWebarchitecture ‒PartofHTTP,URI,andHTMLfamily ‒Request-responseprotocol Onesocketforsinglerequest-response ‒originalspecification ‒havechangedduetoperformanceissues →manyconcurrentrequests →overheadwhenestablishingsameconnections →HTTP1.1offerspersistentconnectionandpipelining HTTPisstateless ‒MultipleHTTPrequestscannotbenormallyrelatedattheserver →"problems"withstatemanagement →RESTgoesbacktotheoriginalHTTPidea Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒23‒ HTTPRequestandResponse RequestSyntax methodurihttp-version<crlf> (header:value<crlf>)* <crlf> [data] ResponseSyntax http-versionresponse-code[message]<crlf> (header:value<crlf>)* <crlf> [data] Semanticsofterms method="GET"|"POST"|"DELETE"|"PUT"|"HEAD"|"OPTIONS" uri=[path][";"params]["?"query] http-version="HTTP/1.0"|"HTTP/1.1" response-code=validresponsecode header:value=validHTTPheaderanditsvalue data=resourcestaterepresentation(hypertext) Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒24‒ ServingHTTPRequest IPandTCPaddressing 1. UserentersURLhttp://company.cz:8080/orderstothebrowser 2. BrowsergetsanIPaddressforcompany.cz,IP:138.232.189.127 3. BrowserandWebServercreatesasocket [147.32.100.5:3223;138.232.189.127:8080] Applicationaddressing 4. BrowsersendsHTTPrequest,thatis,writesfollowingdatatothesocket 1 2 GET/ordersHTTP/1.1 Host:company.cz 5. Webserverpassestherequesttothewebapplicationcompany.czwhichserves GETordersandthatwritesaresponsebacktothesocket. Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒25‒ HTTPListener HTTPlistenerimplementationinJavausingJetty ‒Serverlistensonport8080 ‒JettyparsesHTTPrequestdataintoHttpServletRequestobject. ‒Whenaclientconnects,themethodhandleRequestiscalled ‒Themethodteststhevalueofthehostheaderandrespondsbackifthe headermatchescompany.czvalue. 1 2 3 4 5 6 7 8 9 10 11 12 13 /**handlestherequestwhenclientconnects**/ publicvoidhandleRequest(HttpServletRequestrequest, HttpServletResponseresponse)throwsIOException,ServletException{ //testifthehostiscompany.cz if(request.getHeader("Host").equals("company.cz")){ response.setStatus(200); response.setHeader("Content-Type","text/plain"); response.getWriter().write("Thisistheresponse"); response.flushBuffer(); }else response.sendError(400);//badrequest } Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒26‒ HTTPListener(Cont.) TestitusingTelnet 1 2 3 4 5 6 7 8 9 telnet127.0.0.18080 #...linesomittedduetobrevity GET/ordersHTTP/1.1 Host:company.cz HTTP/1.1201OK Content-Type:plain/text Thisistheresponse... HTTPlistenerinbash ‒UseittotestincommingHTTPconnectionsquickly ‒Usesncutility(netcat) 1 2 3 4 5 6 7 8 9 10 11 12 #ctrl-ctostophttplistener control_c(){ echo-en"\n*Exiting\n" exit$? } trapcontrol_cSIGINT for((;;)) do echo-e"\n\n*Listeningonport$1..." echo-e"\nHTTP/1.0204NoContent\n\n"|nc-l$port done Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒27‒ VirtualWebServer Virtualserver ‒Configurationofanamedvirtualwebserver ‒Webserveruseshostrequestheadertodistinguishamongmultiplevirtual webserversonasinglephysicalhost. ApachevirtualWebserverconfiguration ‒Twovirtualservershostedonasinglephysicalhost 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #allIPaddresseswillbeusedfornamedvirtualhosts NameVirtualHost*:80 <VirtualHost*:80> ServerNamecompany.com ServerAdminadmin@company.com DocumentRoot/var/www/apache/company.com </VirtualHost> <VirtualHost*:80> ServerNamefirm.cz ServerAdminadmin@firm.cz DocumentRoot/var/www/apache/firm.cz </VirtualHost> Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒28‒ BetterSupportforHTTPTesting UsecurltotestHTTPprotocol 1 2 3 4 5 6 7 Usage:curl[options...]<url> -X/--request<command>Specifyrequestcommandtouse -H/--header<line>Customheadertopasstoserver -d/--data<data>HTTPPOSTdata -b/--cookie<name=string/file>Cookiestringorfiletoreadcookiesfrom -v/--verboseMaketheoperationmoretalkative Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 curl-v-H"Host:company.cz"127.0.0.1:8080 *Abouttoconnect()to127.0.0.1port8080 *Trying127.0.0.1...connected *Connectedto127.0.0.1port8080 >GET/HTTP/1.1 >User-Agent:curl/7.20.0(i386-apple-darwin10.3.2)libcurl/7.20.0OpenSSL/0.9.8n >Accept:*/* >Host:company.cz > <HTTP/1.1201OK <Connection:keep-alive <Content-Type:plain/text < <Thisistheresponse... Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒29‒ Overview IntroductiontoApplicationProtocols SimpleProtocolExample IntroductiontoHTTP ‒StateManagement Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒30‒ StateManagement HTTPisastatelessprotocol–originaldesign ‒Noinformationtorelatemultipleinteractionsatserver-side →ExceptAuthorizationheaderiscopiedineveryrequest →IPaddressesdonotwork,onepublicIPcanbesharedby multipleclients Solutionstocheckforavalidstateatserver-side ‒Cookies–obviousandthemostcommonworkaround →RFC2109–HTTPStateManagementMechanism →Allowclientsandserverstotalkinacontextcalledsessions ‒Hypertext–originalHTTPdesignprinciple →Appstatesrepresentedbyresources(hypermedia),linksdefine transitionsbetweenstates →AdoptedbytheRESTprinciplestatelessness Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒31‒ InteractionwithCookies Request-responseinteractionwithcookies ‒Sessionisalogicalchannelmaintainedbytheserver StatefulServer ‒Serverremembersthesessioninformationinaservermemory ‒Servermemoryisanon-persistentstorage,whenserverrestarts thememorycontentislost! Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒32‒ Set-CookieandCookieHeaders Set-Cookieresponseheader 1 2 3 4 5 6 set-cookie="Set-Cookie:"cookie(","cookie)* cookie=NAME"="VALUE(";"cookie-av)* cookie-av="Comment""="value |"Domain""="value |"Max-Age""="value |"Path""="value ‒domain–adomainforwhichthecookieisapplied ‒Max-Age–numberofsecondsthecookieisvalid ‒Path–URLpathforwhichthecookieisapplied Cookierequestheader.Aclientsendsthecookieinarequestif: ‒domainmatchestheoriginserver'sfully-qualifiedhostname ‒pathmatchesaprefixoftherequest-URI ‒Max-Agehasnotexpired 1 2 3 4 cookie="Cookie:"cookie-value(";"cookie-value)* cookie-value=NAME"="VALUE[";"path][";"domain] path="$Path""="value domain="$Domain""="value ‒domain,andpatharevaluesfromcorrespondingattributesoftheSet-Cookie header Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒33‒ SessionManagementJavaClass Managesclientsessionsinaservermemory 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 publicclassSessions<E>{ //storageforthesessiondata; privateHashtable<String,E>sessions=newHashtable<String,E>(); /**Returnssessionidbasedontheinformationinthehttprequest**/ publicStringgetSessionID(HttpServletRequestrequest)throwsException{ Stringsid=null; //extractthesessionidfromthecookie if(request.getHeader("cookie")!=null){ Patternp=Pattern.compile(".*session-id=([a-zA-Z0-9]+).*"); Matcherm=p.matcher(request.getHeader("cookie")); if(m.matches())sid=m.group(1); } //createthesessionidmd5hash;userandomnumbertogenerateaclient-id //notethatthisisasimplesolutionbutnotveryreliable if(sid==null||sessions.get(sid)==null){ MessageDigestmd=MessageDigest.getInstance("MD5"); md.update(newString(request.getRemoteAddr()+ Math.floor(Math.random()*1000)).getBytes()); sid=Utils.toHexString(md.digest()); } returnsid; } publicEgetData(Stringsid)...//returnssessiondatafromsessionsobject publicvoidsetData(Stringsid,Ed)...//setssessiondatatosessionsobject } Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒34‒ StatefulServerImplementation Simpleper-clientcounter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 publicvoidhandleRequest(HttpServletRequestrequest, HttpServletResponseresponse)throwsException{ //getthesessionid Stringsid=sessions.getSessionID(request); //createthenewdataifnoneexists if(sessions.getData(sid)!=null) sessions.setData(sid, Integer.valueOf(sessions.getData(sid).intValue()+1)); else sessions.setData(sid,Integer.valueOf(1)); //sendtheresponse response.setStatus(200); response.setHeader("Set-Cookie","session-id="+sid+";MaxAge=3600"); response.setHeader("Content-Type","text/plain"); response.getWriter().write("Numberofhitsfromyou:"+ sessions.getData(sid).toString()); response.flushBuffer(); } Task ‒Whathappenswhentheserverrestarts? ‒Howdoyouchangethecodetocountrequestsfromallclients? Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒35‒ Testing Testing ‒curlwillrequireyoutospecifycookiesineveryrequest ‒Browserhandlescookiesautomatically 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #runcurlforthefirsttime curl-v127.0.0.1:8080 >GET/HTTP/1.1 >Host:127.0.0.1:8080 > <HTTP/1.1200OK <Set-Cookie:session-id=3a9c3cdc5ff36434aa1ba860727ca401;max-age=3600 < Numberofhitsfromyou:1 #copythecookiesession-idfrompreviousresponse curl-v-bsession-id=3a9c3cdc5ff36434aa1ba860727ca401127.0.0.1:8080 >GET/HTTP/1.1 >Host:127.0.0.1:9900 >Cookie:session-id=3a9c3cdc5ff36434aa1ba860727ca401 > <HTTP/1.1200OK <Set-Cookie:session-id=3a9c3cdc5ff36434aa1ba860727ca401;max-age=3600 < Numberofhitsfromyou:2 Lecture3:ApplicationProtocols,CTUWinterSemester2014/2015,@TomasVitvar ‒36‒