1 2 3 ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ FAKULTA ELEKTROTECHNICKÁ KATEDRA POČÍTAČŮ ROZŠÍŘENÍ INFORMAČNÍHO SYSTÉMU O ROZHRANÍ WEBOVÝCH SLUŽEB (SOAP) BAKALÁŘS...
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ FAKULTA ELEKTROTECHNICKÁ KATEDRA POČÍTAČŮ
ROZŠÍŘENÍ INFORMAČNÍHO SYSTÉMU O ROZHRANÍ WEBOVÝCH SLUŽEB (SOAP) BAKALÁŘSKÁ PRÁCE MAREK DUBA
Vedoucí bakalářské práce: Ing. Jaroslav Klapálek Leden 2012
Abstrakt V dnešní době, kdy informační systém tvoří základ pro úspěšné podnikání téměř každé středně veliké organizace a kdy v České republice máme na desítky různých informačních systémů, stojíme před velkou výzvou, jak tyto systémy efektivně propojit, a tím usnadnit a zkvalitnit předávání dat z jednoho systému do druhého, ať už jde o nákup, prodej, skladovou evidenci, logistiku aj. Tato práce se zabývá implementací komunikačního rozhraní XML/SOAP pro informační systém K2, který vyrábí česká společnost K2 atmitec, s. r. o., V současné době eviduje přes šest set implementací.
Abstract Nowadays, when an information system forms the basis of a successful business in almost every medium-sized organization, and when there are dozens of different information systems in the Czech Republic, we face a big challenge of how to connect these systems efficiently and facilitate and improve data transfer from one system to another, whether in concerned the purchase or sale, stock accounts, logistics, etc. This Bachelor‘s Degree Thesis deals with the problem of implementing a communication interface XML/SOAP for K2 information system producing by a Czech company K2 atmitec, Ltd. Currently this company is registering over six hundred implementations.
Podě ková nı́ Ze všeho nejdříve bych rád poděkoval svojí ženě Lucii za její trpělivost během tvorby této práce. Bez jejích neustálých připomínek bych práci buď nikdy nezačal, nebo bych ji alespoň pořádně nedokončil. Velkou zásluhu na této bakalářské práci mají také vedoucí práce Ing. Jaroslav Klapálek, a oponentka Ing. Božena Mannová, Ph.D., oběma děkuji za metodickou a konzultační pomoc. Dále bych rád poděkoval svým kolegům, kteří se mnou pracují v jednom týmu, který má na starosti informační systém K2. Právě oni, museli toto rozhraní obratem implementovat k našim zákazníkům a poskytovali mi cenné praktické připomínky, a tedy také díky nim je tento projekt podstatně propracovanější.
Prohlá š enı́ Prohlašuji, že jsem tuto bakalářskou práci vypracoval samostatně, a že jsem použil pouze literaturu uvedenou v přiloženém seznamu. V dokumentu je použito názvů firem a produktů, které mohou být chráněny patentovými a autorskými právy nebo mohou být registrovanými obchodními značkami podle příslušných ustanovení právního řádu.
1 UC vod Cílem této práce je na základě získaných znalostí o protokolu SOAP a informačním systému K2 navrhnout a implementovat SOAP rozhraní pro zmíněný informační systém. Tato práce je rozdělena do několika dílčích kapitol. První dvě kapitoly seznamují s protokolem webových služeb (SOAP) a s informačním systémem K2. Následuje kapitola zaměřená na rozbor samotného vývoje nového rozhraní. V poslední kapitole je toto nové rozhraní podrobeno zkoušce pomocí demonstrační aplikace, která má za cíl ukázat XML přístup k datům a transakční operace v informačním systému. Součástí práce je taktéž doprovodné CD se zdrojovými kódy.
9
Bakalářská práce
SOAP
Duba Marek
2 SOAP 2.1 Historie SOAP (Simple Object Access Protocol) lze asi nejlépe vysvětlit na postupu, jak historicky vznikl. Už od počátku WWW (okolo roku 1993) bylo možné zavolat program na webserveru a předat mu textové parametry jednoduše tak, že se na konec URL označujícího program přidal znak „?“ a za něj se uvedly názvy parametrů a jejich hodnoty oddělené znaky „&“. Tento způsob má jedno omezení, a tím je maximální délka URL, činící v praxi 4kB. Proto byla vymyšlena tzv. metoda POST protokolu HTTP, která parametry předává v těle HTTP požadavku. Metodou POST je možné předávat jakákoliv data jakékoliv délky, standardizován byl ale jen typ zvaný application/x-www-form-urlencoded, jehož tvar je shodný s tvarem parametrů předávaných přímo v URL. Postupem času (okolo roku 1997) začaly prohlížeče podporovat i typ multipart/form-data, který umožňuje k textovým parametrům přidat obsah souborů. Vždy bylo možné si metodou POST poslat jakákoliv binární data (například firma SUN doporučovala posílat si tak instance Java objektů mezi appletem a servletem). To však předpokládalo mít kontrolu nad oběma konci HTTP spojení, ale tento požadavek není v obecných aplikacích splněn. S příchodem jazyka XML, který umožňuje zapsat libovolně složitě strukturovaná data do textového souboru platformově nezávislým způsobem, bylo jen otázkou času, než někoho napadlo posílat si metodou POST data v XML. XML má tu výhodu, že se předávaná data nemusí omezovat na text, je možné předávat si i složité objekty a kolekce objektů. Při použití XML Schema lze navíc jednotným, rozšiřitelným a srozumitelným způsobem popsat strukturu a typy předávaných dat. Použitím XML Namespaces lze snadno zamezit kolizím stejných jmen pro různé věci. Pak již stačilo přidat k předávaným parametrům i informaci jakou funkci je třeba zavolat, a protokol pro vzdálené volání funkcí byl na světě.
2.2 Příklad volání Uveďme si příklad, jak takové SOAP volání vlastně vypadá. Mějme například funkci boolean jePrvocislo(long cislo), která má číselný parametr jménem cislo a vrací pravdivostní hodnotu podle toho, zda je parametr prvočíslo nebo ne. Vzhledem k tomu, že webová služba může být implementovaná v jakémkoliv programovacím
11
Bakalářská práce
SOAP
Duba Marek
jazyce, typy long a boolean jsou typy definované v XML Schema a ne nutně přítomné v daném programovacím jazyku. SOAP vyžaduje, aby jméno každé funkce bylo v určitém jmenném prostoru. U objektově orientovaných jazyků tento jmenný prostor odpovídá objektu a funkce jeho metodě, u implementace třeba v jazyku C tento jmenný prostor přímý smysl nemá, ale je nutné nějaký prostor definovat. Jmenný prostor je určen nějakým URI, zvolme třeba urn:mojeURI. <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xs="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"> <env:Header/> <env:Body> <m:jePrvocislo xmlns:m="urn:mojeURI"> 1987
Obrázek 2.1: Příklad SOAP požadavku
Tato zpráva je přenesena na server, který funkci implementuje, obvykle metodou POST protokolu HTTP. Postup, jímž je na serveru nalezena funkce, která má být zavolána, záleží na implementaci serveru, obvykle je funkce určena svým jménem a jmenným prostorem. Další možnosti jsou: URL, na které přišel HTTP požadavek, obsah speciální HTTP hlavičky SOAPAction, uživatelem definovaný obsah tagu env:Header, nebo něco jiného. SOAP je v tomto benevolentní a nepředepisuje jeden povinný způsob. Všimněme si, že v XML představujícím toto SOAP volání jsou zavedeny čtyři prefixy jmenných prostorů: env použitý pro tagy samotného SOAP, xs pro XML Schema použitý v označení typu xs:long, xsi pro instanci XML Schema použitý pro odlišení atributu xsi:type určujícího typ parametru funkce a konečně námi definovaný m použitý pro označení naší funkce. Jak vyplývá z definice XML Namespaces, samotné řetězce použité jako prefixy nejsou důležité a mohly být zvoleny libovolné jiné, důležité jsou jen URI, ke kterým se prefixy vážou. Například místo prefixu env mohl být použit prefix SOAP-ENV, výsledek by to nezměnilo. <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema"> <env:Body> <ns1:jePrvocisloResponse xmlns:ns1="urn:mojeURI" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> true
Obrázek 2.2: Příklad SOAP odpovědi
12
Bakalářská práce
SOAP
Duba Marek
2.3 WSDL Možnost vzdáleně volat funkce pomocí SOAP nemá praktický význam, pokud nevíme, jaké funkce se dají zavolat, jaké mají parametry a jaké vrací hodnoty. Tento problém řeší jazyk WSDL (Web Services Description Language), založený na XML a hojně využívající standardy XML Namespaces a XML Schema. Vznikl sloučením tří jazyků firem IBM, Microsoft a Ariba s názvy NASSL, SCL a SDL do jednoho jazyka nazvaného WSDL 1.1. W3C (World Wide Web Consortium) vzalo WSDL 1.1 na vědomí a ustavilo pracovní skupinu s názvem Web Services Description Working Group, která nyní pracuje na jeho novější verzi WSDL 1.2 (zatím je ve fázi pracovního návrhu). WSDL jazyk je velice obecný, aby zachoval platformovou nezávislost. Popišme si, jak takový popis webové služby ve WSDL vypadá. WSDL dokument obsahuje popis jedné služby (klíčové slovo service), což je největší jednotka. Jedna služba má jednu nebo více bran (port). Každá brána má vazbu (binding), což je způsob, jak se daná brána volá (například SOAP-přes-HTTP), a nějakou přístupovou adresu (URL). Lze tedy teoreticky mít pro jednu službu více bran s různými vazbami, tj. volat jednu službu různými způsoby, např. první SOAP-přes-HTTP, druhou SOAP-přes-HTTP-nad-SSL a třetí SOAP-přes-SMTP. Prakticky budou fungovat jen první dvě, protože WSDL 1.1 má definovánu syntaxi jen pro vazby založené na HTTP. <message name="jePrvocisloRequest"> <part name="cislo" type="xsd:long"/> <portType name="Cisla"> ... <service name="CislaService"> <port binding="m:cislaSoapBinding" name="cisla"> <wsdlsoap:address location="http://nekde.cz/cisla"/>
Obrázek 2.3: Příklad popisu webové služby (WSDL)
Vazby odkazují na rozhraní (portType), které je souhrnem operací (operation). Rozhraní v objektově-orientovaných jazycích odpovídá objektu, jednotlivé operace odpovídají metodám objektu, nebo v jazyce C funkcím. Každá operace definuje obvykle dvě zprávy (message), jednu vstupní a jednu výstupní, ale může definovat i méně. Každá zpráva obsahuje žádnou, jednu nebo více částí (part), které odpovídají parametrům a návratovým hodnotám. Z toho plyne, že volané funkce mohou mít více návratových hodnot než jen jednu! Typy parametrů a návratových hodnot jsou definovány pomocí XML Schema. Jako jednoduché typy jsou tedy k dispozici řetězce (string), čísla s pohyblivou (float) i pevnou (decimal)
13
Bakalářská práce
SOAP
Duba Marek
řádovou čárkou, pravdivostní hodnoty (boolean), binární data (base64Binary), časový okamžik (dateTime), časový interval (duration), URL (anyURI); dále jejich odvozeniny výčtové typy, číselné intervaly, záporná čísla (negativeInteger) a celá čísla různého rozsahu (int, byte, short, long). Je možné definovat složené typy vzniklé jako souhrny nebo varianty z jiných typů, jednoduchých i složených, dokonce lze definovat jeden typ jako rozšíření druhého, lze tedy vyjádřit dědičnost objektů.
2.4 Nástroje pro implementaci Většina moderních vývojových prostředí obsahuje generátory webových služeb. Ty mají za úkol na základě WSDL schématu připravit (vygenerovat) zdrojový kód v daném prostředí, který bude webovou službu implementovat. Takový generátor umí připravit kód jak pro implementaci serveru, tak pro implementaci klienta. Většina vývojových prostředí také podporuje opačný postup. Vývojář tedy zadá název služby a názvy funkcí, které chce službou obsluhovat, a generátor připraví deklarace příslušných metod. Programátor pak metody vybaví vstupními parametry a návratovými hodnotami, implementuje obslužný kód a generátor webové služby zajistí i potřebný WSDL popis.
Obrázek 2.4: Ukázka průvodce pro vytvoření webové služby
Samotná tvorba webových služeb (implementace strany serveru) nebo naopak jejich konzumace (implementace klienta) nevyžaduje v moderním vývojovém prostředí prakticky žádnou znalost o protokolu SOAP nebo popisu webové služby WSDL. Pokud se programátor drží postupu z průvodce při tvorbě webové služby, jednoduše implementuje metody tříd, jako by je konzumovala jiná třída ve stejném programu. Stejně tak i případný konzument webové služby volá průvodcem vygenerované funkce, jako by jejich zpracování obsluhovala instance třídy v jeho vlastním programu.
14
Bakalářská práce
Informační systém K2
Duba Marek
3 Informač nı́ systé m K2 3.1 Představení produktu Informační systém K2 (dále jen IS K2) je komplexní systém pro řízení podniků, který ve svých modulech provázaně řídí činnosti jednotlivých oblastí podnikového řízení. Je nadčasovým softwarem, který splňuje všechny požadavky kladené na informační systémy při současném stavu vývoje IT. Pracuje s jasnou vizí a připraveností na očekávaný vývoj této dynamicky se rozvíjející oblasti v budoucích letech.
Obrázek 3.1: Hlavní plocha IS K2
IS K2 je vhodný pro všechny segmenty firem. Je rozdělen do tří produktů tak, aby plně zabezpečil kvalitu, stabilitu a funkcionalitu, které jednotlivé segmenty požadují a potřebují. Podstatou však zůstává skutečnost, že systém je stále tentýž, tedy má stejnou datovou strukturu i systémovou logiku, a v případě růstu to pro podnik neznamená enormní investice související s přechodem na jiný technologicky a funkčně výkonnější informační systém. Svým uživatelům IS K2 přináší komplexní a provázané řešení v následujících modulech: Prodej, Nákup, Celnice, Sklad, Doprava, Výroba, Finance, Marketing, Kontaktní centrum CRM, Personalistika a mzdy, Účetnictví a analýzy, Majetek,
15
Bakalářská práce
Informační systém K2
Duba Marek
Internetový obchod, Manažerské vyhodnocení – K2 OLAP, Přenos dat a Správce, a to ve čtyřech jazykových mutacích (česky, slovensky, anglicky a německy). Informační systém K2 je možné provázat i s přídavnými moduly, které se také nacházejí v základní nabídce (čtečky, psiony, příslušenství pro čárové kódy, iPAQ apod.). Všechny moduly, stejně jako celý IS K2, je možné provozovat formou outsourcingu.
3.2 Architektura IS K2 je v současné verzi 3 klasická databázová aplikace typu klient-server. Na straně serveru je volnost ve výběru databázové platformy, a to buď Microsoft SQL Server, nebo Oracle Database. Podporovány jsou 32 i 64bit verze serverů. Klient vyžaduje ke svému běhu minimálně operační systém Windows 2000, certifikován je ale také pro nejnovější Windows 7. Podporovány jsou rovněž 32 i 64bit verze operačního systému. Klient je Win32 aplikace, která se dělí z hlediska architektury na následující 3 části: • • •
Kompilovaná část aplikace (jádro – tzv. standard) Skriptová nadstavba, kterou dodává výrobce (tzv. standardní skript nebo sestava) Skriptová nadstavba, kterou dodává integrátor, konzultant, případně sám zákazník (tzv. speciální skript nebo sestava)
3.3 Možnosti přizpůsobení Možnosti přizpůsobení IS K2 jsou široké, téměř až nekonečné. Systém obsahuje vlastní vývojové prostředí pro tvorbu tiskových sestav a vývojové prostředí pro tvorbu tzv. skriptů. Skriptem se rozumí program v jazyce Pascal/Delphi a vývojové prostředí je programátorům, kteří na platformě Pascal/Delphi někdy pracovali, důvěrně známé. Samotná prostředí pro tvorbu speciálních skriptů a sestav, ačkoliv se může zdát, že se jedná o dvě rozdílné věci, se vzájemně doplňují. V sestavě je možné používat programové celky ze skriptu a ve skriptu lze naopak spouštět tiskové sestavy. Jak už bylo řečeno v představení produktu, IS K2 se dělí do několika modulů. V prostředí skriptu se tato architektura jen potvrzuje. Pro práci s datovými strukturami obsahuje jádro aplikace tzv. datové moduly. Datové moduly jsou nadstavbou databázových tabulek, kterých je v databázi IS K2 řádově tři sta. Vazby mezi tabulkami jsou hlídány pouze aplikační logikou, kterou představují právě datové moduly. Každý datový modul tak obsahuje kromě jednoduché obsluhy sekvenčního procházení dat také vazby na další datové moduly, přímo z konkrétních vazebních polí. Pokud tedy potřebujeme ve skriptu pracovat s daty, což je naprostá většina
16
Bakalářská práce
Informační systém K2
Duba Marek
skriptů, používá se přístup přes datové moduly. Kromě čtení umožňuje datový modul také změny dat, vkládání nových řádků, včetně kontroly správnosti vazeb na ostatní datové moduly při uložení, a to vše pod přísným dohledem transakční logiky, čímž je zachována konzistence dat. Pomocí datových modulů lze obsloužit naprostou většinu datového zpracování bez nutnosti znalosti jazyka SQL. Samozřejmostí je také definice vlastních datových tabulek, nad kterými je možné vytvořit vlastní datové moduly, definovat vazby do standardních datových modulů a naopak.
Obrázek 3.2: Editor skriptů
Pokud přístup přes samotné datové moduly nestačí a je zapotřebí pro některé nestandardní implementace přímý přístup k datům, obsahuje vývojové prostředí také nástroje pro SQL dotazování. SQL dotaz je vhodné psát dle doporučení výrobce pomocí speciálních značek, které zajišťují, že dotaz půjde spustit na všech databázových platformách, které IS K2 podporuje. Typickým příkladem je uvození datových polí v dotazu, reprezentace nulových hodnot apod. Pomocí nástroje pro tvorbu skriptů lze také tvořit formuláře, které vzhledově a logicky zapadají do IS K2. K dispozici jsou datové typy předků formulářů, které používá standard, a tak je speciál v mnoha případech k nerozeznání od standardu, což je u takové aplikace cílem. Je také možné do jisté míry upravovat i formuláře standardní.
17
Bakalářská práce
Informační systém K2
Duba Marek
Za nejmocnější nástroj pro modifikaci systému můžeme označit registrované funkce – skripty spouštěné na pozadí systému bez vědomí uživatele. Systém obsahuje v každém modulu tzv. registrované body. Registrovaný bod je místo, kde dochází k zahájení nějaké důležité činnosti – např. načtení dat, uložení, nebo jiná specifická činnost konkrétního datového modulu (typicky potvrzení dokladu). Do takového bodu lze přiřadit jeden nebo více skriptů, které mohou vykonávat nějakou návaznou automatizovanou činnost, nebo je možné činnost, ke které mělo dojít, úplně přerušit. Registrované funkce se používají pro definici nových datových polí a vazeb do standardních modulů, k zabránění činnosti uživatele – pokud vznikly nové nestandardní podmínky pro hlídání změn apod. Zbývá zmínit nástroj pro tvorbu tiskových sestav. Pro jejich tvorbu je trochu jiné grafické prostředí než pro tvorbu skriptů, ale na pozadí sestavy lze vykonávat stejné funkce a rutiny jako ve skriptu. Sestava může jako zdroj dat použít datový modul včetně k němu vázaných datových modulů, výsledek SQL dotazu nebo jinak programově připravenou datovou strukturu. Nad tímto zdrojem dat sestava zobrazuje vybraná datová pole. Z datových polí lze tvořit počítaná pole, agregační pole, a tyto pak umisťovat v grafickém návrháři na sestavu. Za pomoci grup lze vytvořit několika úrovňové master-detail zobrazení přesně podle přání uživatele.
Obrázek 3.3: Editor sestav
Všechny tyto možnosti přizpůsobení IS K2 mají velikou výhodu v tom, že vývojová prostředí jsou součástí klientské aplikace, a tak lze speciální funkce vyvíjet
18
Bakalářská práce
Informační systém K2
Duba Marek
a ladit přímo u zákazníka nad jeho daty. Tím významně dochází ke zkrácení doby nasazení nových implementací.
3.4 Komunikační rozhraní V systému je nepřeberné množství způsobů komunikace s okolním světem (se systémy třetích stran). Ve skriptu je k dispozici klasické rozhrání pro práci s textovým souborem, CSV souborem, tabulkou aplikace Excel apod. V portfoliu možností je také přístup k externím databázovým zdrojům. Ty můžou být buď stejného typu, nad kterým lze IS K2 provozovat (Microsoft SQL Server, Oracle Database), k těm se přistupuje pomocí vestavěného driveru, nebo to můžou být díky integraci svobodné (open-source) knihovny Zeos zcela odlišné databázové platformy (např. MySQL, Firebird, Sybase a další). Samozřejmostí je ve skriptu také podpora dnes oblíbeného formátu XML. Všechny tyto a další možnosti spolu se skriptovacím nástrojem umožňují IS K2 komunikovat s okolním světem prakticky neomezeně, co se týče definice datových struktur. Základem k úspěšné komunikaci s okolím je tedy skript, který obsluhuje dohodnutou datovou strukturu a na straně IS K2 provádí výměnu dat a transakční zpracování požadavků. Nyní se podívejme, jakým způsobem může takový skript běžet. Nejjednodušším způsobem je jeho spuštění v aplikaci K2. Tím se ovšem dá uspokojit jen ruční dávkové zpracování (např. import cen od dodavatele). Pokud potřebujeme provádět takovou dávku automaticky, máme možnost v K2 naplánovat úlohu. Pro spouštění naplánovaných úloh v K2 musíme zajistit trvalý běh aplikace K2, nebo alespoň K2 plánovaně zapnout v čase před spuštěním plánované úlohy. To se může zdát trochu kostrbaté, proto výrobce dodává, společně s každou kompilací aplikace, také její variantu pro běh v příkazové řádce jménem SCRIPTEX. Je to vlastně jádro IS K2, obsahující všechny datové moduly a standardní programové vybavení pro běh všech nevizuálních komponent. Ve SCRIPTEXu běžný uživatel nepracuje, ale pokud je skript (ať už speciální nebo standardní) napsán tak, že ke svému běhu nevyžaduje žádné vizuální komponenty (nebo je alespoň pomocí direktiv kompilátoru pro běh ve SCRIPTEXu vylučuje), můžeme takový skript spouštět právě pomocí aplikace SCRIPTEX rovnou z příkazové řádky. Takové spuštění už snadno naplánujeme pomocí standardních nástrojů operačního systému. Mezi další komunikační vybavení patří také experimentální podpora ActiveX (DCOM), takže je možné přistupovat k datovým modulům a objektům jádra IS K2 přímo z aplikace třetí strany. Jelikož tento modul vznikl jen jako experimentální projekt, výrobce ho k produkčnímu nasazení nedoporučuje.
19
Bakalářská práce
Informační systém K2
Duba Marek
Rozhraní, které mě zaujalo nejvíce, neboť umožňuje spouštění skriptů v reálném čase při obdržení požadavku, je implementace K2 ISAPI. Jedná se o knihovnu DLL pro internetovou informační službu (IIS). Ta umožňuje pomocí K2 skriptu vytvořit dynamický webový obsah podobně jako ASP nebo PHP. Díky úzké vazbě na výrobce mi bylo umožněno blíže nahlédnout do „kuchyně“ tohoto rozhraní a rozhodl jsem se vydat právě touto cestou při tvorbě vlastního rozhraní pro obsluhu požadavků webové služby. K2 ISAPI totiž nedělá nic jiného, než že si na počátku, při inicializaci, spustí určité množství SCRIPTEXů (vláken IS K2) a těmi pak střídavě obsluhuje jednotlivé klienty HTTP (vlákna na vstupu K2 ISAPI). Se SCRIPTEXem pak K2 ISAPI komunikuje pomocí Windows rozhraní Named Pipes (pojmenovaných rour).
20
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
4 Tvorba rozhranı́ pro IS K2 4.1 Architektura Základem rozhraní je komponenta zvaná Broker, viz Obrázek 4.1. Tato komponenta je stěžejním prvkem této práce a jejím vývojem se budeme zabývat v další části této kapitoly. Cílem Brokeru je poskytnout směrem ke klientovi standardní rozhraní WSDL/SOAP. Směrem do Informačního systému K2 pak zajišťovat volání odpovídajícího speciálního skriptu, který je pro obsluhu konkrétní funkce webové služby napsán. Broker je tedy ve směru ke klientovi zcela standardní WSDL/SOAP implementace. Základem je běžný HTTP server naslouchající na daném TCP portu, který lze nastavit. Každý požadavek od klienta znamená nové běžící vlákno, ve kterém je požadavek vybaven, a po jeho vybavení dojde k ukončení vlákna i HTTP spojení. cmp Model komponent
WSDL/SOAP klient
PipeHandler
WSDL/SOAP
«delegate»
IS K2
K2 SCRIPTEX Jádro IS K2 Broker WSDL/SOAP
PipeHandler
«use» «use» Skript pro obsluhu SOAP v olání
«use»
K2 DB
Obrázek 4.1: Diagram komponent
Na straně IS K2 funguje Broker podobně jako rozhraní K2 ISAPI zmíněné v předchozí kapitole. Broker si sám při startu zajistí spuštění nevizuální K2 – SCRIPTEXu ve speciálním režimu pro spouštění skriptů přes Windows rozhraní Named Pipes. Jeden SCRIPTEX znamená jedno běžící vlákno IS K2. Aby Broker mohl zpracovávat požadavky simultánně, je zapotřebí spustit SCRIPTEXy alespoň dva nebo tři. Kolik se jich má při startu spustit, lze určit v konfiguračním souboru. Úkolem Brokeru je tedy seřadit vlákna na vstupu, která přichází z rozhraní WSDL/SOAP do fronty, a tuto frontu pak postupně odbavovat jednotlivými vlákny IS K2 – SCRIPTEXem. Každá funkce webové služby musí mít v K2 připraven speciální skript, který je napsán právě pro obsluhu této funkce. Broker sám tedy s daty IS K2
21
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
nijak nepracuje, všechny požadavky vyřizuje speciální skript, odladěný v K2 editoru skriptů, jako by byl určen přímo pro běh v K2. Tímto způsobem je nyní otevřená cesta ke zpracování jakéhokoliv požadavku on-line v K2, kterého bude zapotřebí pro komunikaci a výměnu dat se softwarem třetích stran. Zároveň je veškerá činnost pod dohledem datových modulů a aplikační logiky jádra K2, takže nemůže být narušena konzistence dat. Možnost odpovídat na WSDL/SOAP požadavky K2 skriptem je vítána i ze strany stávajících konzultantů, neboť se pro ně práce ve skriptu nijak zvlášť nemění a přispívá tak ke zkrácení doby zpřístupnění nových funkcí na webovou službu.
4.2 Vývoj rozhraní – vymezení požadavků Na počátku vývoje jsem si určil cíle a požadavky, které má nové rozhraní splňovat. Přihlédl jsem přitom ke zkušenostem z minulých projektů, které jsem realizoval v minulosti na vývojové platformě Pascal/Delphi, a vzhledem k tomu, že celý Informační systém K2 je napsán v prostředí Embarcadero Delphi 2009, rozhodl jsem se využít stejnou pro mě známou vývojovou platformu. V té době společnost, ve které pracuji, zakoupila licenci na Embarcadero RAD Studio XE Professional (balík vývojových produktů, který obsahuje i zmíněné Delphi) a vývoji na této platformě již nestálo nic v cestě. Přejděme tedy ke klíčovým požadavkům na nové rozhraní, kterými byly: •
Broker musí být kompilován jako služba typu Win32
Pro uspokojení tohoto požadavku mi byla doporučena od kolegy komponenta SvCom od společnosti ALDYN Software. Standardně sice Delphi obsahuje komponenty pro vývoj Win32 služeb, ale SvCom přináší nespočet výhod, mezi které patří zejména rozšířené ladění služby za běhu. S vestavěnou standardní komponentou pro vývoj služeb je totiž zapotřebí nejdříve zkompilovanou službu spustit ve správci služeb a poté se k běžící službě připojit z vývojového prostředí. To je sice dobrý koncept, ale není vhodný pro všechny situace. Zejména spouštění služby se tímto způsobem neladí dobře. •
Splňovat standard služeb typu WSDL/SOAP
Vývojové prostředí Embarcadero Delphi XE již obsahuje ucelenou sadu průvodců pro vytvoření příkladného rozhraní tohoto typu. Stačí pouze založit do projektu nový modul WSDL/SOAP a průvodce se postará o vygenerování potřebného kódu. Pro definici služby (WSDL) vytvoří průvodce šablonu nové třídy, do které se deklarují potřebné metody včetně vstupních a výstupních parametrů. Dle definice této třídy se dynamicky generuje popis služby (WSDL) a zároveň tato třída slouží jako
22
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
automatický parser SOAP požadavků od klienta, takže se vývojář zabývá pouze obsluhou funkce v jazyce svého vývojového prostředí a vůbec tak nemusí přijít do styku s XML, jak už bylo zmíněno v kapitole o SOAP. •
Podpora pro obsluhu více instancí K2
Někteří zákazníci IS K2 provozují systém ve více instancích. Takový případ nastává typicky v majetkově provázaných subjektech, kde skupina firem (typicky matka – dcera) obchodují společně na trhu a každá z nich má svoji vlastní instanci K2. Tím spíš, když jedna ze společností působí na Slovensku a má i odlišnou daňovou a obchodní legislativu. Ať už je důvod pro běh více instancí jakýkoliv, jedním z požadavků na rozhraní je umožnit přístup do všech instancí K2 z jedné instance Brokeru. Řešení je prosté – u funkcí, které zastřešují více instancí IS K2, je povinný parametr název instance, do které je požadavek směrován, a Broker dle konfiguračního souboru pošle požadavek do jednoho z volných vláken (SCRIPTEXu) požadované instance K2. •
Pro každou instanci K2 předem definovaný počet vláken
Jeden běžící SKRIPTEX (vlákno K2) znamená jednu obsazenou licenci systému. Některým zákazníkům se tedy bude hodit omezit počet běžících vláken například na dvě nebo v případech, kdy webová služba zpracovává málo požadavků, dokonce na jedno běžící vlákno. Kolik vláken zákazník bude ve skutečnosti potřebovat, záleží na počtu požadavků za minutu a na složitosti (rychlosti) zpracování funkce ve vyvolaném skriptu. •
Víceúrovňové logování
Samozřejmostí je také logování, které má umožnit sledování činnosti Brokeru během počátku uvedení do provozu. Každá funkce Brokeru musí v důležitých bodech průběžně zapisovat do logu. Funkce pro zápis do logu má obsahovat také parametr, kterým se rozliší, o jakou úroveň logování se jedná. V konfiguračním souboru se pak zadá požadovaná úroveň logování, kterou bude Broker za běhu zapisovat do logovacího souboru.
23
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
4.3 Vývoj rozhraní – objektový model V této kapitole se podíváme blíže na objektový model nového rozhraní. Pojmenování jednotlivých tříd vychází z obecné konvence pojmenování tříd ve vývojovém prostředí Delphi: • • • • • • • •
TMainService – Win32 služba – hlavní třída TLogFile – třída pro víceúrovňové logování TMandant – třída objektu jedné instance IS K2 TMandants – kolekce tříd TMandant TScriptex – třída objektu reprezentující běžící SCRIPTEX (vlákno IS K2) TScriptexes – kolekce tříd TScriptex TScriptexControlThread – vlákno, které kontroluje běh SCRIPTEXu TScriptexRestartThread – vlákno, které restartuje SCRIPTEX v případě potřeby
Dále projekt obsahuje třídy, které vytvořil průvodce pro implementaci serverové části SOAP rozhraní: • •
TK2WebModule – potomek třídy TWebModule – obecný parser pro zpracování HTTP požadavku a odpovědi (TWebRequest, TWebResponse) TGetK2Data – potomek třídy TInvokableClass – parsování, validace a implementace funkcí zveřejněných ve WSDL služby SOAP
Další část této kapitoly se věnuje rozboru jednotlivých tříd podrobněji a pojednává o zajímavých částech zdrojového kódu. Kompletní zdrojový kód je na přiloženém CD. 4.3.1
procedure FinalizeService; public { Public declarations } end;
Obrázek 4.2: Deklarace třídy TMainService
TMainService je potomkem TNtService. Reprezentuje Win32 službu a její metody implementují události, které na Win32 službě mohou nastat: • • • •
NtServiceStart – spuštění služby NtServiceStop – zastavení služby NtServiceCreate – volá se v konstruktoru služby NtServiceDestroy – volá se v destruktoru služby
Metoda NtServiceCreate volá konstruktory instancí dalších nezbytných objektů potřebných pro běh služby. NtServiceDestroy pak analogicky zajišťuje destrukci instancí těchto objektů. procedure TMainService.NtServiceStart(Sender: TNtService; var DoAction: Boolean); begin LoadIni; Log.Write('Service is going to start PID:' + IntToStr(GetCurrentProcessId)); try InitializeService; WebBroker.Active := True; Log.Write('Web Broker listening'); Log.Write('Service started'); except Log.Write('Error during starting the service'); end; end;
Obrázek 4.3: Metoda NtServiceStart
Při spuštění služby se nejdříve načítá nastavení z inicializačního souboru – metoda LoadIni. Ve většině metod si prosím povšimněte obsluhy logovacího souboru pomocí instance objektu Log. V chráněném bloku (try – except) se dále volá metoda InitializeService, kterou v dalším bloku probereme podrobněji. Pokud doběhne InitializeService bez chyby, aktivuje se WebBroker – začne naslouchat příchozím SOAP požadavkům na zvoleném TCP portu. procedure TMainService.InitializeService; var Len: Cardinal; CurrentUser: string; i: integer; begin Len := 80; SetLength(CurrentUser, Len); if Log.Via(WNetGetUser(nil, PWideChar(CurrentUser), Len) = NO_ERROR, 'WNetGetUser') then Log.Write('Current user - ' + PChar(CurrentUser));
25
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
if (Win32Platform = VER_PLATFORM_WIN32_NT) and (Win32MajorVersion <= 4) then Log.Via(RevertToSelf, 'NT - 3 or 4 detected - RevertToSelf'); LogonResult := LogonUser(PChar(ScriptUser), PChar(ScriptDomain), PChar(ScriptPass), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, hToken); Log.Via(LogonResult, 'Logon ' + ScriptDomain + '\' + ScriptUser); if not Log.Via(ImpersonateLoggedOnUser(hToken), 'Impersonate') then Exit; Mandants.hToken := hToken; Log.Write('Initialization finished'); for i := 0 to Mandants.Count - 1 do Mandants[i].InitializeProcesses; end;
Obrázek 4.4: Metoda InitializeService
Na počátku inicializace je čistě pro kontrolu zjištěn aktuální uživatel, pod kterým je spuštěn hlavní proces (služba) – ten se zapíše do logovacího souboru. Následuje výjimka pro starší verze Windows NT, kde je proces služby standardně impersonifikován (tzn. že vlákno, ve kterém běží služba, se vydává za klientskou aplikaci). Toto nastavení zvrátí WinAPI funkce RevertToSelf. Pokračuje se přihlášením uživatele, který je uveden v inicializačním souboru jako uživatel, pod kterým poběží SCRIPTEX. Pokud se přihlášení pomocí WinAPI funkce LogonUser povede, Handle přihlášeného uživatele je uložen do proměnné hToken, se kterou se dále pracuje při spuštění SCRIPTEXu. Obsah hToken se také předává do stejnojmenné proměnné v kolekci Mandants (globální objekt třídy TMandants vytvořený v NtServiceCreate). Nakonec inicializace se zavolá inicializace procesů všech mandantů (instancí IS K2). Tuto metodu dále rozebereme v podrobném náhledu třídy TMandant. procedure TMainService.NtServiceStop(Sender: TNtService; var DoAction: Boolean); begin Log.Write('Service is going to stop'); WebBroker.Active := false; Log.Write('Web Broker closed'); try FinalizeService; if LogonResult then Log.Via(CloseHandle(hToken), 'Close token'); Log.Write('Service stopped'); except Log.Write('Error during stopping the service'); end; end;
Obrázek 4.5: Metoda NtServiceStop
26
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
Obsluha zastavení služby nejdříve ze všeho deaktivuje WebBroker. Ten přestane naslouchat SOAP požadavkům a služba se může začít vypínat. V chráněném bloku se volá metoda FinalizeService, kterou není třeba podrobněji komentovat. V této metodě se pro každou instanci třídy TMandant z kolekce TMandants zavolá metoda FinalizeProcess, kterou probereme podrobně v náhledu třídy TMandant. [GLOBAL] LOG=C:\broker.log SCRIPTDOMAIN=MAREK SCRIPTUSER=Administrator SCRIPTPASS=passw0rd [MANDANT1] NAME=BAL SCRIPTEXDIR=C:\K2\ SCRIPTDIR=C:\K2\SESTAVYW\Special\K2R MAXRUNSCRIPTS=3
Obrázek 4.6: Ukázka inicializačního souboru
Pro úplnost ještě připojuji příklad Inicializačního souboru. V Sekci GLOBAL se definují globální parametry, jako je cesta k logovacímu souboru a přihlašovací údaje pro uživatele, pod kterým běží všechny SCRIPTEXy. Dalších sekcí MANDANT(x) může inicializační soubor obsahovat více, podle toho, kolik instancí IS K2 se bude obsluhovat. 4.3.2
TLogFile je potomkem základní třídy TObject. Představuje logovací soubor a definuje metody pro logování:
27
Bakalářská práce
• •
Tvorba rozhraní pro IS K2
Duba Marek
FWrite – interní metoda pro zapisování do logovacího souboru Write, WriteError, Via – veřejné variace metody FWrite
Konstruktor a destruktor třídy TLogFile vytváří (resp. ruší) instanci třídy TCriticalSection, která se používá ke sdílenému přístupu do logovacího souboru. Veřejné metody Write, WriteError a Via netřeba podrobněji rozebírat, jsou to jen variace volání interní metody FWrite, které usnadňují práci s logovacím souborem. procedure TLogFile.FWrite(Text: string; LogLevel: integer); var Handle, i: Integer; aTime: string; begin if FileName = '' then Exit; if LogLevel > Self.LogLevel then Exit;
FCS.Enter; try if FileExists(FileName) then begin Handle := FileOpen(FileName, fmOpenReadWrite); FileSeek(Handle, 0, 2); end else Handle := FileCreate(FileName); // Formátování řídících znaků i := 1; repeat if Text[i] < #32 then begin Insert(Format('#%.2x', [Byte(Text[i])]), Text, i + 1); Delete(Text, i, 1); Inc(i); end; Inc(i); until i > Length(Text); DateTimeToString(aTime, FormatSettings.LongTimeFormat + ':zzz', Now); Text := aTime + ';' + IntToStr(GetCurrentThreadId) + ';' + Text + #13#10; FileWrite(Handle, PAnsiChar(AnsiString(Text))^, Length(Text)); FileClose(Handle); finally FCS.Leave; end; end;
Obrázek 4.8: Metoda FWrite
Na začátku metody pro zápis se testuje, jestli se má zapisovat požadovaný LogLevel. Pokud je v globálním parametru LogLevel vyšší číslo než v předaném parametru metody, k zalogování dojde. Viz deklarace konstant pro úroveň logování před deklarací třídy TLogFile. Pokračuje se vstupem do kritické sekce. Je to vlastně semafor, který má zabránit tomu, aby do této části programu vstoupilo jiné vlákno. Soubor je sdílený
28
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
prostředek, se kterým může pracovat jen jedno vlákno, proto je celý tento blok ošetřen kritickou sekcí, navíc v chráněném bloku, takže kdyby došlo k jakékoliv chybě, třeba při zápisu do souboru, kritická sekce se ve výstupním bloku finally opustí. V chráněném bloku je už jen obsluha zápisu do logovacího souboru, kde se ke každé zprávě přidává datum a čas. Zpráva navíc může obsahovat řídící znaky pro SCRIPTEX, které nemají žádnou čitelnou ASCII reprezentaci, takže se tyto řídící znaky formátují na viditelné desítkové číslo uvozené křížkem. Pro samotnou práci se souborem se využívají funkce WinAPI – FileOpen, FileSeek, FileWrite a FileClose. 10:16:21:562;1212;MT;Service is going to start PID:1196 10:16:21:578;1212;MT;WNetGetUser - success. 10:16:21:578;1212;MT;Current user - SYSTEM 10:16:47:253;1212;MT;Logon MAREK\Administrator - success. 10:16:47:253;1212;MT;Impersonate - success. 10:16:47:253;1212;MT;Initialization finished 10:16:47:253;1212;BAL1;InitializeSecurityDescriptor - success. 10:16:47:253;1212;BAL1;SetSecurityDescriptorDacl - success. 10:16:47:253;1212;BAL1;Create ScriptEx start control event - success. 10:16:47:300;1212;BAL1;CreateProcess - C:\K2\SOAP\ScriptEx.exe - success.
Obrázek 4.9: Ukázka logovacího souboru
Na závěr ponoru do třídy TLogFile nahlédněte na výslednou podobu logovacího souboru. Kromě aktuálního data a času je také uvedeno číslo vlákna, neboť ta se mohou v zápisu do logovacího souboru střídat, a v případě hledání chyby je nutné vědět, ke kterému vláknu řádek vlastně patří. 4.3.3
TMandant
type TMandant = class(TCollectionItem) private FScriptexes: TScriptexes; FName: string; FMaxRunScripts: integer; FInitialized: boolean; FScriptexFileName: string; FScriptPath: string; FScriptexPipeName: string; FServicePipeName: string; FScriptexReady: array of THandle; // semafory na skriptex je volný function GetLogFile: TLogFile; function GetScriptexDir: string; function NameId(Id: integer): string; procedure procedure procedure procedure procedure
function CreateScriptProcess(id: integer): boolean; function CloseHandleViaLog(Handle: THandle; Text, ProcId: string): Boolean; function GetScriptDir: string;
TMandant je potomek TCollectionItem, neboť se používá ve spojení s kolekcí TMandants, jinak bychom si vystačili s potomkem základní třídy TObject. U této třídy se budeme blíže zabývat těmito metodami: • • • •
InitializeProcess – inicializace vláken IS K2 CreateScriptProcess – spuštění SCRIPTEXu (vlákna IS K2) FinalizeProcess – ukončení vláken IS K2 ExecuteScript – spuštění skriptu IS K2
procedure TMandant.InitializeProcesses; var i: integer; begin if not CreateScriptProcess(0) then begin Log.Write('!Cannot initialize processes!', LOG_LEV_ERROR, NameId(0)); Exit; end; for i := 1 to FMaxRunScripts - 1 do if not CreateScriptProcess(i) then raise Exception.Create('!!Processes initialization failed!!'); FInitialized := true; end;
Obrázek 4.11: Metoda InitializeProcess
Metoda InitializeProcess v rámci jednoho mandanta (Instance IS K2) zajistí spuštění tolika SCRIPTEXů (vláken IS K2), kolik jich je nastaveno v parametru MaxRunScripts v inicializačním souboru. Pokud všechno proběhne tak jak má, nahodí se příznak FInitialized na true a Mandant je připraven ke spouštění skriptů. Blíže se podíváme na metodu CreateScriptProcess, která se v rámci inicializace volá. function TMandant.CreateScriptProcess(id: integer): boolean; var CreateProcEvent, PipeHandle: THandle;
30
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
SecurityDescriptor: PSECURITY_DESCRIPTOR; SecurityAttributes: TSecurityAttributes; ReleaseHandles: Boolean; StartupInfo: TStartupInfo; ProcInf: TProcessInformation; begin ReleaseHandles := false; PipeHandle := INVALID_HANDLE_VALUE; Result := false; GetMem(SecurityDescriptor, SECURITY_DESCRIPTOR_MIN_LENGTH); try if not Log.Via(InitializeSecurityDescriptor(SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION), 'InitializeSecurityDescriptor', LOG_LEV_GENERAL, NameId(id)) then Exit; if not Log.Via(SetSecurityDescriptorDacl(SecurityDescriptor, True, nil, False), 'SetSecurityDescriptorDacl', LOG_LEV_GENERAL, NameId(id)) then Exit; SecurityAttributes.nLength := SizeOf(SecurityAttributes); SecurityAttributes.lpSecurityDescriptor := SecurityDescriptor; SecurityAttributes.bInheritHandle := True; CreateProcEvent := CreateEvent(@SecurityAttributes, true, false, nil); if not Log.Via(CreateProcEvent <> 0, 'Create ScriptEx start control event', LOG_LEV_GENERAL, NameId(id)) then Exit; try FillChar(StartupInfo, SizeOf(StartupInfo), 0); StartupInfo.cb StartupInfo.dwFlags StartupInfo.wShowWindow StartupInfo.dwXCountChars StartupInfo.dwYCountChars
:= := := := :=
SizeOf(StartupInfo); STARTF_USESHOWWINDOW; SW_HIDE; id + 1; // Mandanti se číslují od 1 CreateProcEvent;
FillChar(ProcInf, SizeOf(ProcInf), 0);
Obrázek 4.12: Metoda CreateScriptProcess – začátek
Až do této chvíle probíhala příprava ke spuštění procesu. Jedná se o ucelenou sadu WinAPI funkcí, které je potřeba zavolat, abychom měli k dispozici příslušné objekty pro spuštění WinAPI funkce CreateProcessAsUser. Ta pro své spuštění vyžaduje instance tříd TSecurityAttributes a TStartupInfo. Ve StartupInfo lze nastavit způsob spuštění procesu a také se zde využívá s výrobcem IS K2 dohodnutý parametr dwYCountChars, kde se předává adresa na Handle semaforu, který jsme vytvořili pomocí WinAPI funkce CreateEvent. K tomuto semaforu se dostaneme ještě dále v této metodě. Dále je zapotřebí mít připravenou instanci třídy TProcessInformation, kam CreateProcessAsUser vrací Handle vlákna a procesu po úspěšném spuštění. // Spouštění procesu ScriptEx if not Log.Via(CreateProcessAsUser(TMandants(Collection).hToken, nil, PChar(Format('%s ISAPI=%s', [FScriptexFileName, FScriptexPipeName])), @SecurityAttributes, @SecurityAttributes, True, NORMAL_PRIORITY_CLASS, nil, PChar(ScriptexDir), StartupInfo, ProcInf), 'CreateProcess - ' + FScriptexFileName, LOG_LEV_GENERAL, NameId(id)) then Exit;
31
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
Log.Write('ScriptEx PID:' + IntToStr(ProcInf.dwProcessId), LOG_LEV_GENERAL, NameId(id)); ReleaseHandles := true; // Ověření úspěšného startu Log.Write('Waiting for ScriptEx start control event ...', LOG_LEV_GENERAL, NameId(id)); if not Log.Via(WaitForSingleObject(CreateProcEvent, 20000) = WAIT_OBJECT_0, 'ScriptEx start control event is set', LOG_LEV_GENERAL, NameId(id)) then Exit; finally CloseHandleViaLog(CreateProcEvent, 'ScriptEx start control event', NameId(id)); end;
Obrázek 4.13: Metoda CreateScriptProcess – 1. pokračování
Po zavolání CreateProcessAsUser se čeká na úspěšný start SCRIPTEXu pomocí WinAPI funkce WaitForSingleObject. Funkce vyčká zadaný časový limit na nahození semaforu CreateProcEvent. To je instance zmiňovaného semaforu, jehož Handle jsme předali pomocí dwYCountChars do SCRIPTEXu. Ten, jakmile dojde k úspěšnému přihlášení, tento semafor nahazuje a naše služba se tak dozví, že je všechno v pořádku a může se pokračovat dál. Pokud se služba nahození semaforu nedočká, inicializace končí. // Připojení na trubku ScriptEx PipeHandle := CreateFile(PChar(Format(PipeName, [FServicePipeName, IntToStr(id + 1)])), GENERIC_WRITE + GENERIC_READ, FILE_SHARE_READ + FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); if not Log.Via(PipeHandle <> INVALID_HANDLE_VALUE, 'Connect to ScriptEx pipe', LOG_LEV_GENERAL, NameId(id)) then Exit; ReleaseHandles := False; FScriptexes[id].FPipe := PipeHandle; FScriptexes[id].FThread := ProcInf.hThread; FScriptexes[id].FProcess := ProcInf.hProcess; ResetEvent(FScriptexes[id].FFails[sfPipeClose]); FScriptexes[id].FFails[sfProcEnd] := ProcInf.hProcess; FScriptexes[id].FControlThread := TScriptexControlThread.Create(FScriptexes[id]); FScriptexes[id].FReady := true; SetEvent(FScriptexReady[id]); Log.Write('ScriptEx initialization finished', LOG_LEV_GENERAL, NameId(id));
Obrázek 4.14: Metoda CreateScriptProcess – 2. pokračování
Pokud dojde k úspěšnému nahození semaforu, musí se naše služba ještě připojit k otevřené Windows Named Pipe, kterou SCRIPTEX otevřel. To se provede pomocí WinAPI funkce CreateFile. S Windows Named Pipe se pracuje stejně jako se standardním souborem, WinAPI pozná Named Pipe pouze podle specifického názvu souboru. S výrobcem K2 je dohodnutá konvence názvu \\.\pipe\%s\ScriptExPipe%s, kde první parametr %s obsahuje cestu k programu SCRIPTEX a druhý parametr %s
32
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
uživatelsky definovaný název. Pokud se podaří i tento krok a Named Pipe je úspěšně otevřen, služba si poznačí nezbytné Handles pro budoucí ovládání spuštěného procesu do odpovídajícího prvku kolekce TScriptexes. Dojde také ke spuštění kontrolního vlákna TScriptexControlThread, které má za úkol sledovat, zda proces SCRIPTEXu stále žije. Poté se SCRIPTEX označí za připravený, aby do něj metoda ExecuteScript mohla odesílat požadavky. finally FreeMem(SecurityDescriptor, SECURITY_DESCRIPTOR_MIN_LENGTH); if ReleaseHandles then begin if PipeHandle <> INVALID_HANDLE_VALUE then CloseHandleViaLog(PipeHandle, 'PipeHandle', NameId(id)); CloseHandleViaLog(ProcInf.hProcess, 'Process', NameId(id)); CloseHandleViaLog(ProcInf.hThread, 'Thread', NameId(id)); end; end; Result := True; end;
Obrázek 4.15: Metoda CreateScriptProcess – konec
Na konci metody dojde k uvolnění nepotřebných prostředků a v případě, že nedošlo ke korektnímu spuštění SCRIPTEXu (nebyl nahozen semafor), se spuštěný proces SCRIPTEXu, který se nemůže přihlásit do IS K2, zabije. procedure TMandant.FinalizeProcesses; var h: THandle; i: integer; begin if not FInitialized then Exit; for i := 0 to FScriptexes.Count - 1 do begin FScriptexes[i].FShutDown := true; h := FScriptexes[i].FPipe; if h <> INVALID_HANDLE_VALUE then begin FScriptexes[i].WriteToPipe(cmTerminate); FScriptexes[i].WriteToPipe(cmEndOfCommand); end; end; for i := 0 to FScriptexes.Count - 1 do begin h := FScriptexes[i].FProcess; if h = INVALID_HANDLE_VALUE then Continue; WaitForSingleObject(h, INFINITE); CloseHandleViaLog(h, 'Process', NameId(i)); CloseHandleViaLog(FScriptexes[i].FThread, 'Thread', NameId(i)); end; for i := 0 to FScriptexes.Count - 1 do begin h := FScriptexReady[i]; if h <> 0 then CloseHandleViaLog(h, 'ProcEvents', NameId(i)); h := FScriptexes[i].FPipe; if h <> INVALID_HANDLE_VALUE then CloseHandleViaLog(h, 'PipeHandle', NameId(i)); end; end;
Obrázek 4.16: Metoda FinalizeProcess
33
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
Metoda FinalizeProcess iteruje přes všechny instance TScriptex v kolekci TScriptexes a postupně je ukončuje. Ukončení probíhá zapsáním příkazu do Named Pipe k odhlášení z IS K2. V následném cyklu se čeká na ukončení procesu opět pomocí WinAPI WaitForSingleObject. Jakmile je proces ukončen, uvolní se i ostatní prostředky. function TMandant.ExecuteScript(Request: string; ScriptName: string): string; var Response, Buffer, CommandStr: AnsiString; id: integer; PipeHandle: THandle; Command: AnsiChar; Len: Cardinal; begin Result := ''; repeat id := WaitForMultipleObjects(MaxRunScripts, @FScriptexReady[0], False, INFINITE) - WAIT_OBJECT_0; FScriptexes[id].FCS.Enter; try if not FScriptexes[id].FReady then Continue; ResetEvent(FScriptexReady[id]); FScriptexes[id].FReady := False; Break; finally FScriptexes[id].FCS.Leave; end; until False;
Obrázek 4.17: Metoda ExecuteScript – začátek
Metoda ExecuteScript obsluhuje více vláken – volá se z obsluhy SOAP požadavku. Každému požadavku je zde přiřazeno jedno volné vlákno z IS K2 následujícím způsobem. Na začátku metody se volá WinAPI funkce WaitForMultipleObjects. Jejím parametrem je pole Handlů na semafory a výstupem je ID semaforu, který byl právě nahozen. Tím získáme ID volného vlákna IS K2. Jelikož na výstup z této funkce může čekat více vláken, zahajuje se kritická sekce. V kritické sekci, kam může vstoupit jen jedno vlákno, se semafor opět shodí a nastaví se ještě pomocná proměnná FReady, že vlákno IS K2 je obsazeno. Kritická sekce se ukončí, a pokud na jejím vstupu čeká další vlákno, bude nyní vpuštěno dovnitř. Na začátku kritické sekce ale další vlákno zjistí pomocí FReady, že vlákno IS K2 je už obsazeno, a musí se tedy vrátit na začátek a čekat na uvolnění jiného vlákna IS K2. Nyní, když máme vyhrazené potřebné prostředky, přejdeme k dalšímu bloku ExecuteScript. try PipeHandle := FScriptexes[id].FPipe; Command := cmNoCommand; Log.Write('Allocated process', LOG_LEV_DETAIL, NameId(id)); if not FScriptexes[id].WriteToPipe(cmExecuteStript + FScriptPath + ScriptName) then Exit; if not FScriptexes[id].WriteToPipe(cmEndOfCommand) then Exit; Response := '';
Obrázek 4.18: Metoda ExecuteScript – 1. pokračování
34
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
Jako první se do Named Pipe zapíše jméno skriptu, který v K2 chceme spustit. SCRIPTEX si příkaz z Named Pipe přečte, otevře soubor se skriptem, provede jeho kompilaci a spustí ho. Další blok řeší komunikaci se spuštěným skriptem během jeho běhu. repeat SetLength(Buffer, BufferSize); if not Log.Via(ReadFile(PipeHandle, PAnsiChar(Buffer)^, BufferSize, Len, nil), 'ReadFile', LOG_LEV_DETAIL, NameId(id)) then Exit; SetLength(Buffer, Len); Log.Write('ReadFile = ' + Buffer, LOG_LEV_DETAIL, NameId(id)); if Len > 0 then case Buffer[1] of cmEndOfFile: Break; cmNoCommand: Delete(Buffer, 1, 1); cmEndOfCommand: begin case Command of cmRequest_GetStringVariable: // požadavek na request CommandStr := Request; cmResponse_Exception: // příznak chyby begin Response := XMLError(CommandStr); Continue; end; end; if not FScriptexes[id].WriteToPipe(Command + CommandStr) then Exit; if not FScriptexes[id].WriteToPipe(cmEndOfCommand) then Exit; CommandStr := ''; Continue; end else begin Command := Buffer[1]; Delete(Buffer, 1, 1); CommandStr := CommandStr + Buffer; Continue; end; end; Response := Response + Buffer; until False; if Length(Response) > 0 then Result := Response;
Obrázek 4.19: Metoda ExecuteScript – 2. pokračování
V tomto cyklu se čeká na jakýkoliv zápis do Named Pipe pomocí WinAPI funkce ReadFile. Jakmile SCRIPTEX do Named Pipe zapíše nějaký obsah, v cyklu se rozparsuje a zjistí se, co skript požaduje. Jediné, co naše implementace SOAP potřebuje, je předání řetězce s XML požadavkem. Jakmile si tedy skript řekne o zaslání řetězcové hodnoty, služba mu zasílá celé vstupní XML. Tato čekací smyčka se ukončí buď přečtením příkazu „skript končí“, nebo pádem aplikace SCRIPTEX. finally if WaitForMultipleObjects(2, @FScriptexes[id].FFails[TScriptexFails(0)], false, 0) = WAIT_TIMEOUT then begin // Uvolnit ScriptEx pro další dotazy pouze pokud nemá žádnou chybu
Výstup z funkce je tedy ošetřen i na případný pád. Kontrolní pole „zdraví“ SCRIPTEXu (FFails) obsahuje dvě hodnoty: semafor ukončení Named Pipe a Handle na běžící proces. Při ukončení se toto pole „zdraví“ jednorázově bez čekání přečte pomocí WaitForMultipleObjects a pokud není nastaven semafor ukončení Named Pipe a proces stále žije, dojde k uvolnění SCRIPTEXu pro další spuštění skriptu. 4.3.4
TScriptex je opět potomkem TCollectionItem. Objekt udržuje jen aktuální stav spuštěného SCRIPTEXu a jediná důležitá metoda je WriteToPipe. function TScriptex.WriteToPipe(Text: string): Boolean; var Len: Cardinal; begin Result := AMandant.Log.Via(WriteFile(FPipe, PAnsiChar(AnsiString(Text))^, Length(Text), Len, nil), Format('WriteFile(''%s'')', [Text]), LOG_LEV_DETAIL, AMandant.NameId(Index)); if not Result then SetEvent(FFails[sfPipeClose]); end;
Obrázek 4.22: Metoda WriteToPipe
36
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
Metoda využívá WinAPI funkci WriteFile a každý zápis do Named Pipe se díky této metodě loguje. Musíme mít ale nastavenou úroveň logu na detail. 4.3.5
Obrázek 4.23: Deklarace třídy TScriptexControlThread
TScriptexControlThread je potomkem TThread a slouží ke kontrole běžícího SCRIPTEXu. Ke spuštění tohoto vlákna dochází po úspěšném spuštění SCRIPTEXu v metodě TMandant.CreateScriptProcess. procedure TScriptexControlThread.Execute; var fail: TScriptexFails; begin AScriptex.AMandant.Log.Write('ScriptEx control thread started', LOG_LEV_GENERAL, AScriptex.AMandant.NameId(AScriptex.Index)); fail := TScriptexFails(WaitForMultipleObjects(2, @AScriptex.FFails[TScriptexFails(0)], false, INFINITE) - WAIT_OBJECT_0); // něco je se skriptexem, nastavím busy AScriptex.FCS.Enter; try ResetEvent(AScriptex.AMandant.FScriptexReady[AScriptex.Index]); AScriptex.FReady := False; finally AScriptex.FCS.Leave; end; AScriptex.AMandant.Log.Write('ScriptEx control thread event: ' + IntToStr(Ord(fail)), LOG_LEV_GENERAL, AScriptex.AMandant.NameId(AScriptex.Index)); end;
Obrázek 4.24: Metoda Execute
V metodě Execute je nekonečné čekání na chybu SCRIPTEXu pomocí WinAPI funkce WaitForMultipleObjects. Jakmile je proces SCRIPTEXu ukončen nebo došlo k neočekávanému ukončení Named Pipe, nastaví se na této instanci příznak, že SCRIPTEX pracuje, a zapíše se záznam do logovacího souboru. procedure TScriptexControlThread.ThreadTerminate(Sender: TObject); begin AScriptex.AMandant.Log.Write('SkriptEx control thread terminated', LOG_LEV_GENERAL, AScriptex.AMandant.NameId(AScriptex.Index)); if AScriptex.FShutDown then Exit; // při vypínání nerestartovat AScriptex.AMandant.Log.Write('Going to restart ScriptEx', LOG_LEV_GENERAL, AScriptex.AMandant.NameId(AScriptex.Index)); AScriptex.FRestartThread := TScriptexRestartThread.Create(AScriptex); end;
Obrázek 4.25: Metoda ThreadTerminate
37
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
Po skončení metody Execute následuje vykonání metody ThreadTerminate, kde se podle toho, zda se jedná o nečekané nebo záměrné ukončení SCRIPTEXu, spustí instance vlákna TScriptexRestartThread. Ta případně zajistí opětovné spuštění SCRIPTEXu. 4.3.6
Obrázek 4.26: Deklarace třídy TScriptexRestartThread
TScriptexRestartThread je potomkem TThread a slouží k restartování nečekaně ukončeného SCRIPTEXu. Ke spuštění tohoto vlákna dochází při ukončení kontrolního vlákna v metodě TScriptexControlThread.ThreadTerminate. procedure TScriptexRestartThread.Execute; begin AScriptex.AMandant.Log.Write('ScriptEx restart thread started', LOG_LEV_GENERAL, AScriptex.AMandant.NameId(AScriptex.Index)); AScriptex.AMandant.CreateScriptProcess(AScriptex.Index); end;
Obrázek 4.27: Metoda Execute
V hlavním těle TScriptexRestartThread se zapíše do logovacího souboru informace o tom, že restartovací vlákno bylo úspěšně spuštěno, a zavolá se metoda TMandant.CreateScriptProcess, která zajistí opětovné standardní spuštění nového SCRIPTEXu. 4.3.7
TK2WebModule
type TK2WebModule = class(TWebModule) HTTPSoapDispatcher: THTTPSoapDispatcher; HTTPSoapPascalInvoker: THTTPSoapPascalInvoker; WSDLHTMLPublish: TWSDLHTMLPublish; procedure K2WebModuleDefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); private { Private declarations } public { Public declarations } end;
Obrázek 4.28: Deklarace třídy TK2WebModule
Tato třída včetně její metody K2WebModuleDefaultHandlerAction je generovaný kód vytvořený pomocí průvodce pro vytvoření serverové implementace SOAP rozhraní.
38
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
procedure TK2WebModule.K2WebModuleDefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin WSDLHTMLPublish.ServiceInfo(Sender, Request, Response, Handled); end;
Obrázek 4.29: Metoda K2WebModuleDefaultHandlerAction
Metoda K2WebModuleDefaultHandlerAction včetně jejího těla je také generovaný kód a není třeba se jím podrobněji zabývat. 4.3.8
TGetK2Data
type TGetK2Data = class(TInvokableClass, IGetK2Data) public function GetK2Data(Data: string): string; stdcall; end;
Obrázek 4.30: Deklarace třídy TGetK2Data
Tato třída je sice také generovaný kód, ale odpovídá již jasným požadavkům na implementaci serverové části SOAP rozhraní. Třída implementuje rozhraní, které vzešlo z průvodce. type IGetK2Data = interface(IInvokable) ['{020ECB66-24B9-4FE2-8F2F-CB97FC78AAB9}'] function GetK2Data(Data: string): string; stdcall; end;
Obrázek 4.31: Deklarace rozhraní IGetK2Data
Toto je vygenerovaná deklarace rozhraní, jež jsme nadefinovali v průvodci pro vytvoření serverové části SOAP rozhraní. Jedná se o funkci, která bude volat jeden skript IS K2, kterému se předá řetězcová hodnota XML a jeho výstupem bude opět XML řetězec. Tímto způsobem jsme schopni implementovat jakékoliv rozšíření nové webové služby bez nutnosti přegenerování WSDL/SOAP rozhraní. Nyní prozkoumáme podrobněji implementaci této jediné publikované funkce. function TGetK2Data.GetK2Data(Data: string): string; var Mandant: string; XML: TXMLDocument; i: integer; Parameters: IXMLParametersType; bErr: boolean; begin Mandants.Log.Write('******************** START ********************'); Mandants.Log.Write('REQUEST: ' + Data); try Mandant := ''; XML := TXMLDocument.Create(nil); try XML.LoadFromXML(Data); Parameters := GetFunction(XML).Parameters; bErr := true; for i := 0 to Parameters.Count - 1 do if Parameters[i].Name = 'Plant' then
39
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
begin Mandant := Trim(Parameters[i].NodeValue); bErr := false; Break; end; finally XML := nil; // Uvolnění ztrátou reference end;
Obrázek 4.32: Metoda GetK2Data - začátek
Na počátku GetK2Data probíhá zápis požadavku do logovacího souboru. Abychom zachovali možnost spouštět skript pro více mandantů (instancí IS K2), bude povinným parametrem zasílaného XML parametr se jménem Plant (mandant). Rozhraní parametr přečte a zajistí spuštění skriptu ve správné instanci IS K2. if bErr then begin Result := XMLError('Není zadán parametr Plant'); Exit; end; bErr := true; for i := 0 to Mandants.Count - 1 do if Mandants[i].Name = Mandant then begin bErr := false; Result := Mandants[i].ExecuteScript(Data, 'GetK2Data.pas'); Break; end;
Obrázek 4.33: Metoda GetK2Data – pokračování
Pokud se podaří takového mandanta najít dle nastavení inicializačního souboru, dojde ke spuštění dříve popsané metody TMandant.ExecuteScript. Tato metoda vrátí jako výsledek návratovou hodnotu přímo ze skriptu IS K2. if bErr then begin Result := XMLError('Mandant "' + Mandant + '" neexistuje'); Exit; end; Mandants.Log.Write('RESPONSE: ' + Result); Mandants.Log.Write('******************** FINISH ********************'); except on E:Exception do Result := XMLError('Během volání on-line funkce nastala neočekávaná vyjímka'); end; end;
Obrázek 4.34: Metoda GetK2Data – konec
Na konci metody GetK2Data se opět zapisuje do logovacího souboru, tentokrát výsledná XML odpověď. Je zde také ošetřena případná obecná výjimka za běhu programu.
40
Bakalářská práce
Tvorba rozhraní pro IS K2
Duba Marek
4.4 Vývoj rozhraní – dokončení Výsledkem předchozího úsilí je Win32 aplikace běžící jako služba se standardním rozhraním webové služby. Na službu je možné se připojit standardním webovým prohlížečem a stáhnout si popis WSDL. Díky popisu WSDL lze implementovat konzumenta webové služby na jakékoliv platformě a volat dohodnuté funkce prostřednictvím XML.
Obrázek 4.35: Domovská stránka webové služby
Tímto způsobem je také implementován testovací SOAP klient, zmíněný v následující kapitole. Jeho kompletní zdrojový kód je součástí přiloženého CD. <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xs="http://www.w3.org/2001/XMLSchema" name="IGetK2Dataservice" targetNamespace="http://tempuri.org/" xmlns:tns="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"> <message name="GetK2Data0Request"> <part name="Data" type="xs:string"/> <message name="GetK2Data0Response"> <part name="return" type="xs:string"/> <portType name="IGetK2Data">
Obrázek 4.37: Nainstalované rozhraní jako Win32 služba
42
Bakalářská práce
Demonstrační implementace
Duba Marek
5 Demonstrač nı́ implementace Obsahem této kapitoly je demonstrační implementace XML přístupu k číselníku zboží a transakční operace založení nové objednávky přijaté do IS K2.
Obrázek 5.1: Číselník zboží
5.1 Skript v IS K2 Demonstrační přístup do IS K2 má tedy tyto dvě základní funkce: • •
5.1.1
GetZbozi – vrátí kompletní číselník zboží včetně aktuální dispozice na expedičním skladu a katalogovou cenu AddObjednavka – založí novou objednávku přijatou v modulu prodej a vrátí její číslo. Požadavek obsahuje seznam zboží a požadované množství k objednání
GetZbozi
Funkce vrátí kompletní číselník zboží. Vstupním parametrem je mandant a číslo zákazníka. Výstupem je seznam zboží obsahující tyto údaje: číslo, zkratka, název, měrná jednotka, aktuální dispozice v měrné jednotce a cena pro zadané číslo zákazníka v Kč. Následuje příklad XML pro požadavek a odpověď.
Na straně K2 je nyní potřebné mít připravený skript, který volá rozhraní webové služby. V tomto skriptu je nutné v hlavní metodě nejdříve stáhnout vstupní XML z Named Pipe, a na základě jmen funkcí v rozparsovaném XML, zavolat odpovídající metody. function Main(Request: string): string; var XML: TXmlIO; Funkce: string; begin XML := TXmlIO.Create; try XML.LoadXMLString(Request); XML.Reset; XML.DoNext('Function'); Funkce := XML.AE.getAttribute('name'); case Funkce of MF_GetZbozi: Result := GetZbozi(XML.Xml); MF_AddObjednavka: Result := AddObjednavka(XML.Xml); end; finally XML.Free; end; end; begin WriteLn(Main(WebRequest.Query)); end;
Obrázek 5.4: Metoda Main a její spuštění
Komunikace s Named Pipe probíhá pomocí funkcí WebRequest.Query – načítá řetězcovou hodnotu z Named Pipe (v SOAP rozhraní metoda
44
Bakalářská práce
Demonstrační implementace
Duba Marek
TMandant.ExecuteScript) a WriteLn – odesílá řetězcovou hodnotu zpět do SOAP rozhraní, kde je čtena opět v cyklu uvnitř metody TMandant.ExecuteScript. Metoda Main se tedy zavolá pomocí WriteLn(Main(WebRequest.Query));. Tímto způsobem se předá vstupní XML přímo do metody Main a výstupní parametr se po skončení zapíše na výstup Named Pipe pomocí WriteLn. Hlavní blok metody main prochází strukturu XML a hledá root node s názvem Function. Ten má ve svém parametru název funkce, která se má zavolat. V našem případě tedy dojde k zavolání metody GetZbozi. function GetZbozi(Request: string): string; var XMLRequest: TXMLRequest; XMLResponse: TXMLGetZboziResponse; Zbozi: TZbozi; CDo: integer; ErrT: boolean; begin ErrT := true; XMLRequest := TXMLRequest.Create(MF_GetZbozi); try // XML Parsing XMLRequest.LoadFromString(Request); // Čtení parametrů - vrací string CDo := StrToInt(XMLRequest.ParamByName(MP_CDo)); // V tuto chvíli už vstupní XML není potřebné finally XMLRequest.Free; end; // Odpověď XMLResponse := TXMLGetZboziResponse.Create(MR_GetZboziResponse); Zbo.CreateDM; Zbo.OpenDM(omNormal); Zbo.AktIndex := ZBO_By_Cis; DmStav(Zbo, sdmRada); try Zbo.SetFirstDM; while Zbo.DoNextDM do begin if Zbo.Typ_Zbo <> ZBO_Typ_Zbozni then Continue; // jen zboží karty Zbozi := TZbozi(XMLResponse.ZboziList.Add); Zbozi.Cis := Zbo.Cis; Zbozi.Zkr := Trim(Zbo.Zkr); Zbozi.Naz := Trim(Zbo.Naz); Zbozi.MJ := LinkFStr(Zbo, 'Alt_J;Zkr'); Zbozi.Disp := Zbo.CFDisp; Zbozi.Cena := Zbo.ProdejniCena(0, CDo, 0, '', false); end; ErrT := false; finally if ErrT then XMLResponse.errorText := 'Neznámá chyba'; try Result := XMLResponse.SaveToString; finally XMLResponse.Free; Zbo.Free; end; end; end;
Obrázek 5.5: Metoda GetZbozi
45
Bakalářská práce
Demonstrační implementace
Duba Marek
V K2 se ke vstupnímu XML už přistupuje pomocí sady připravených tříd, které slouží jako XML parser. Nejdříve se založí odpovídající instance XMLRequest, do které se vstupní XML načte pomocí metody LoadFromString(Request); to je standardní funkce K2, která parsuje vstupní XML dle definice třídy. type TXMLRequest = class(TXMLParent) private Fname: string; FParameters: TScriptCollection; function GetParameters: TScriptCollection; public function ParamByName(name: string): string; published property name: string read Fname write Fname {$DESC 'AsAttribute'}; property Parameters: TScriptCollection read GetParameters write FParameters {$DESC 'ItemName=Parameter'}; end;
Obrázek 5.7: Příklad objektového XML parseru TParameter
V příkladech tříd pro XML parsování jsou důležité hlavně speciální direktivy {$DESC} pomocí kterých se parsování řídí. Tím lze nadefinovat, zda je vlastnost třídy brána jako další XML node, či vlastnost rodičovského nodu, nebo lze například definovat jméno nodu potomka. V metodě GetZbozi se tedy neprochází struktura XML, tak jako to dělá obecná metoda Main. Pracuje se s instancemi tříd, které jsou potomkem TCollection a TCollectionItem z prostředí Delphi. GetZbozi otevírá standardní datový modul zboží, který se pomocí konstrukce SetFirst – DoNext sekvenčně projde a naplní se výstupní objektová struktura XML. K transofrmaci do výstupního XML dojde na konci metody pomocí XMLResponse.SaveToString; Tímto metoda končí, dojde k předání na výstup až do původního volání WriteLn a výstupní XML si přečte SOAP rozhraní z Named Pipe. 5.1.2
AddObjednavka
Funkce vrátí číslo nové objednávky přijaté. Vstupním parametrem je mandant a číslo zákazníka, následovaný seznamem zboží.
Metoda AddObjednavka používá standardní datový modul objednávky přijaté. Popis práce s datovým modulem zde nebudeme rozebírat, to by vydalo na publikaci podobného rozsahu. Přepis zdrojového kódu AddObjednavka je zde proto, aby byla vidět analogie s funkcí GetZbozi. Na počátku dojde k parsování XMLRequest, jehož instance zůstává přístupná po celou dobu funkce, neboť v části vytváření objednávky je potřeba mít přístup k seznamu zboží ze vstupního XML. Po úspěšném vytvoření objednávky je její číslo předáno do výstupního XML. V tuto chvíli máme založen nový doklad objednávky přijaté včetně požadovaných položek ze vstupního XML. Následující obrázek ukazuje tiskovou sestavu v K2, která byla spuštěna nad nově vzniklou objednávkou.
48
Bakalářská práce
Demonstrační implementace
Duba Marek
Obrázek 5.11: Náhled na vytvořenou objednávku přijatou
5.2 Testovací prostředí Pro živé demonstrace je součástí práce virtuální stroj (PC s nainstalovaným IS K2, SOAP rozhraním a demonstrační implementací), na kterém je možné veškerou činnost, zde popisovanou otestovat. Podrobný návod, jak se na virtuální stroj připojit, naleznete v příloze této práce, která je dostupná pouze v tištěné podobě na doprovodném CD. Tento virtuální stroj bude dostupný pouze na dobu určitou, do obhajoby této práce, z důvodu licenčního omezení.
49
Bakalářská práce
Demonstrační implementace
Duba Marek
Na testovacím stroji jsou umístěny veškeré potřebné nástroje k otestování funkčnosti. Na ploše je zástupce na spuštění K2, aplikace SOAP test, která umožňuje do SOAP rozhraní zasílat vzroky XML volání, které jsou rovněž uloženy na testovacím stroji. Po spuštění SOAP Test můžete načíst testovací XML tlačítkem Load XML a následným stiskem tlačítka Get K2 Data můžete otestovat funkci SOAP rozhraní, které na požadavek odpoví.
Obrázek 5.12: SOAP test - klient SOAP rozhraní
Rovněž můžete nahlédnout také do informačního systému K2, do kterého se přihlásíte uživatelským jménem K2, bez hesla.
50
Bakalářská práce
Závěr
Duba Marek
6 Zá vě r Takto dokončené rozhraní bylo ihned zavedeno do praxe hned ve dvou projektech. První zákazník, který využívá informační systém K2, byl osloven nabídkou na systém řízení skladu pomocí čárového kódu. Tento systém třetí strany vyžaduje online komunikaci s IS v reálném čase a přímo podporuje právě rozhraní webových služeb. Programovaný je přitom na platformě Microsoft .net a s naším rozhraním komunikuje několik požadavků za minutu bez problémů. Druhý zákazník, také dlouholetý uživatel informačního systému K2 byl osloven nabídkou na internetový obchod, který bylo nutné propojit s K2 kvůli vytváření objednávek, synchronizaci číselníků a skladových zásob. On-line komunikace s K2 nebyla sice přímo vyžadována, ale díky existenci SOAP rozhraní se přímo nabízela. Rozhraním denně projde na sto nových objednávek. Během implementací vznikly také nové požadavky, které jsou předmětem dalšího vývoje do budoucna. Zejména je to nutnost přenést zodpovědnost za validní XML přímo na webovou službu, aby se mohli do rozhraní publikovat i veřejné funkce pro klienty, kteří jsou schopni se připojit k webové službě vlastními silami. Prozatím je rozhraní omezeno tím, že k jeho provozu je nutná pouze jedna funkce, která vždy předává celé XML do K2 skriptu a za validaci je tak odpovědný K2 skript. Takže na počátku nové implementace je potřeba dohodnout pevné XML struktury, od kterých se nesmí ani jedna strana odchýlit. Mezi další nové požadavky je zařazeno například automatické navyšování vláken IS K2, nebo správa služby pomocí administrační aplikace, aj. S odstupem času hodnotím toto rozhraní jako přínos roku 2011.
51
Bakalářská práce
Duba Marek
Literatura •
•
• • •
• •
POLÁCH, EDUARD: Pravidla sazby diplomových prací. České Budějovice, Pedagogická fakulta Jihočeské univerzity, 1998 (aktualizováno 26. 1. 2000). URL: http://home.pf.jcu.cz/~edpo/pravidla/pravidla.html. KUBA, MARTIN: Web Services. Brno, Ústav výpočetní techniky Masarykovy univerzity, (aktualizováno 14. 11. 2011) URL: http://www.ics.muni.cz/bulletin/articles/269.html. K2 atmitec, s. r. o.: K2 software – svět K2. Ostrava, (poslední aktualizace není známa) URL: http://www.k2atmitec.cz/cz/produkty/software/svet.htm. K2 atmitec, s. r. o.: Informační systém K2 – Základní příručka pro verzi 3.130. Verze dokumentace 011, Ostrava, duben 2009. DYNNIKOV, ALEXEY (ALDYN Software): Simple NT Service Example. (poslední aktualizace nezjištěna) URL: http://www.aldyn-software.com/examples/001-SimpleService/index.html. MANDYS, TOMÁŠ: How to implement web services for INDY. Version 1.0 URL: http://www.scribd.com/doc/6550904/Indy-Soap-Web-Services-in-Delphi. MICROSOFT: MSDN Library (nápověda pro WinAPI) URL: http://msdn.microsoft.com/en-us/library/default.aspx.
Obsah př ilož ené ho CD • • • • •
52
\BP \k2_wms_broker \k2_wms_test \XML readme.txt
bakalářská práce ve formátu PDF a MS Word zdrojové kódy SOAP rozhraní zdrojové kódy programu SOAP test příklady volání demonstračních funkcí z kapitoly 5.1 návod na připojení k testovacímu stroji z kapitoly 5.2