SOAP extenze pro komprimaci komunikace

24. července 2003

V předchozím článku jsem popsal základní rysy SOAP extenzí a jejich význam. Také jsme společně vytvořili SOAP extenzi, která sledovala komunikaci mezi klientem a serverem. Užitečnost SOAP extenzí ovšem ještě více vynikne, pokud nechceme SOAP zprávy jen pasivně sledovat, ale chceme je například komprimovat nebo šifrovat pro zvýšení bezpečnosti a snížení nároků na komunikaci mezi klientem a serverem.

Základní požadavky na SOAP extenzi komprimující data

Komprimace SOAP zpráv je zvláště výhodná v případě výměny velkých datových sad (datasetů) mezi klientem a serverem, ale SOAP extenzi je samozřejmě možné použít s libovolnou službou. Naše SOAP extenze by měla splňovat následující požadavky:

  • Veškerá komunikace mezi klientem a serverem je komprimována.
  • V SOAP zprávě je uložena informace, že se jedná o komprimovanou zprávu.
  • Pokud je zaslána nekomprimovaná zpráva, SOAP extenze se nesnaží zprávu dekomprimovat.

Z požadavků plyne, že je nutné komprimovat a dekomprimovat SOAP zprávy na serveru i na klientovi, proto musí být SOAP extenze nainstalována na serveru i u klienta (konzumenta) WWW služby. To je rozdíl ve srovnání se SOAP extenzí pro sledování komunikace mezi klientem a serverem, která byla nainstalována pouze na serveru.

Pořadí a význam fází při volání metody ProcessMessage se u klienta liší od pořadí a významu fází na serveru. Jak jsme již řekli minule, v jaké fázi se právě nacházíme, zjistíme z vlastnosti Stage třídy SoapMessage, jejíž instance je jediným argumentem metody ProcessMessage. Vlastnost Stage obsahuje hodnotu z enumerace SoapMessageStage, jejíž členy a jejich význam u klienta si nyní popíšeme přesně tak, jak jsme to udělali u serveru.

Hodnota enumerace Popis
BeforeDeserialize Byla přijata odpověď ze serveru, ale ještě nebyla deserializována. Toto je fáze, ve které musíme zprávu dekomprimovat, aby mohl .NET Framework pracovat se standardní SOAP zprávou.
AfterDeserialize Odpověď ze serveru byla deserializována, to znamená, že z instancí XSD typů v SOAP zprávě byly vytvořeny korespondující instance typů .NET Frameworku.
BeforeSerialize Požadavek na server ještě nebyl serializován.
AfterSerialize Požadavek na server byl úspěšně vytvořen a serializován. V této fázi provedeme komprimaci SOAP zprávy.

Pro komprimaci použijeme volně šiřitelnou knihovnu SharpZipLib. Zkomprimovaná data jsou do SOAP zprávy zapsána v kódování Base64. SOAP extenze bude do každé zprávy při komprimaci přidávat hlavičku (SOAP Header) s názvem IsCompressed, podle které lze spolehlivě poznat, že se jedná o komprimovanou zprávu. Komprimováno smí tedy být pouze tělo zprávy (obsah elementu soap:Body), ale ne hlavičky zprávy (soap:Header elementy)!

Komprimace a dekomprimace pomocí knihovny SharpZipLib vybočuje z tématu článku, zájemci si mohou dokumentaci ke knihovně stáhnout ze serveru SourceForge.net.

Vytváříme extenzi

Protože je kód SOAP extenze poměrně rozsáhlý, budu v článku popisovat jen nejdůležitější třídy a metody – nejvíce se naučíte prostudováním přiloženého zdrojového kódu. Pokud se psaním SOAP extenzí zabývat nechcete a zajímá vás jen použití SOAP extenze ve vašich projektech, můžete použít výše zkompilovanou verzi SOAP extenze, kterou také naleznete ve výše zmíněném archivu.

Při vytváření SOAP extenze musíme nejdříve vytvořit pomocné funkce pro komprimaci. Abychom nezatěžovali SOAP extenzi implementací algoritmu pro komprimaci a dekomprimaci, vytvoříme pomocnou třídu s názvem ZipUtilities, která bude obsahovat dvě veřejné metody. Metoda ZipSoapBody komprimuje obsah předaného Streamu, metoda UnzipSoapBody analogicky dekomprimuje:

public virtual Stream ZipSoapBody (Stream inputStream)
  {
    XmlTextReader myReader = new XmlTextReader(inputStream);
    XmlDocument myDocument = new XmlDocument();
    myDocument.Load(myReader);
    XmlNamespaceManager myManager = new XmlNamespaceManager(myDocument.NameTable);
    myManager.AddNamespace(„soap“, „http://schemas.xmlsoap.org/soap/envelope/“);
    XmlNode bodyNode = myDocument.SelectSingleNode(@“//soap:Body“,myManager);
    String originXML = bodyNode.InnerXml;
    // Nic ke komprimaci
    if(originXML.Length == 0)
      {
        return inputStream;
      }
    XmlNode headerNode = myDocument.SelectSingleNode(@“//soap:Header“, myManager);
    if (headerNode == null)
      {
        headerNode = myDocument.CreateElement(„soap:Header“, „http://schemas.xmlsoap.org/soap/envelope/“);
        bodyNode.ParentNode.InsertBefore(headerNode,bodyNode);
      }
    XmlElement compressedHeader = myDocument.CreateElement(„IsCompressed“);
    headerNode.AppendChild(compressedHeader);
    string zipXML = this.zipString(originXML);
    bodyNode.InnerXml = zipXML;
    MemoryStream retStream = new MemoryStream();
    myDocument.Save(retStream);
    return retStream;
  }

Metodě je předán Stream obsahující SOAP zprávu, jež má být zkomprimována. Nejprve je vytvořen XmlTextReader, do kterého je načten předaný Stream, a poté je z XmlTextReaderu vytvořen XmlDocument. V XML dokumentu je vyhledán element soap:Body. Pokud je prázdný, je vrácen původní Stream a k žádné komprimaci nedojde. Pokud prázdný není, přidáme „hlavičku“ IsCompressed a v privátní metodě ZipString provedeme samotnou komprimaci. Obsah (přesněji řečeno hodnotu vlastnosti InnerXml) elementu soap:Body nahradíme zkomprimovaným řetězcem. Změněný obsah XML dokumentu uložíme do nového Streamu, který z metody vrátíme.

Ve třídě ZipUtilities je také veřejná vlastnost ZipLevel, která určuje stupeň komprimace. Může nabývat hodnot od jedné do devíti. Bohužel, knihovna SharpZipLib obsahovala v době vzniku tohoto článku chybu, která se projevovala při komprimaci velkého množství dat a současně při úrovni komprimace menší nebo rovné pěti – proto vám doporučuji tuto vlastnost nevyužívat a nechat standardně přednastavenou úroveň „devět“.

Po napsání pomocné třídy vytvoříme SOAP extenzi. Nejprve přepíšeme obě verze metody pro prvotní inicializaci SOAP extenze:

public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
  {
    return attribute;
  }
public override object GetInitializer(Type t)
  {
    return new ZipExtensionAttribute();
  }

První verze metody je volána v případě zaregistrování SOAP extenze k WWW službě pomocí atributu ZipExtensionAttribute. Metoda pouze příslušný atribut vrátí. Druhá verze je volána v případě, že SOAP extenze byla zaregistrována k WWW službě v konfiguračním souboru web.config. Metoda vytvoří novou instanci atributu a vrátí ji. Obě metody vrací instanci třídy ZipExtensionAttribute, která je potomkem třídy SoapExtensionAttribute a která je použita nejen k asociování naší SOAP extenze s WWW metodou, ale také navíc obsahuje vlastnost ZipLevel, jež určuje úroveň komprimace. Vytváření atributů je triviální a bylo podrobně popsáno v předchozím článku.

V přepsané metodě ProcessMessage jsou v příslušných fázích volány pomocné privátní funkce pro komprimaci a dekomprimaci, které používají třídu ZipUtilities. Instance třídy ZipUtilities je vytvořena v metodě Initialize při každém požadavku na WWW službu. Ve fázi AfterSerialize je SOAP zpráva zkomprimována, ve fázi BeforeDeserialize je zpráva dekomprimována.

Tím je SOAP extenze pro komprimaci hotova. Jak se používají SOAP extenze na serverové straně, bylo vysvětleno v předchozím článku, nyní se zaměříme na použití SOAP extenzí u klienta. Na klientovi musí být nainstalována knihovna se SOAP extenzí i knihovna SharpZipLib. Nejprve musíte vytvořit proxy třídu WWW služby. Nejjednodušší je přidání reference na WWW službu ve VS.NET, případně můžete proxy vytvořit manuálně pomocí programu wsdl.exe z .Net Framework SDK.

wsdl /out:MyServicer.cs <URL popisu služby>

Přepínač out určuje jméno výsledné proxy třídy, parametr <URL popisu služby> musí obsahovat cestu k WSDL popisu vaší služby. Do vygenerované proxy třídy přidáte k metodám atribut Interval.SoapExtensions ZipExtensionAttribute a do konkrétního projektu (ASP.NET, Win Forms) přidáte referenci na knihovnu IntervalExtensions. Upozorňuji, že po aktualizaci reference na WWW službu se všechny ručně doplněné informace z proxy třídy ztratí, musíte proto atributem Interval.SoapExtensions ZipExtensionAttribute dekorovat metody znovu.

[Interval.SoapExtensions.ZipExtension]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(„http://tempuri.org/GetData“, RequestNamespace=“http://tempuri.org/“, ResponseNamespace=“http://tempuri.org/“, Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public System.Data.DataSet GetData()
{
  object[] results = this.Invoke(„GetData“, new object[0]);
  return ((System.Data.DataSet)(results[0]));
}

Extenzi je samozřejmě možné ještě vylepšovat. Když přijde na server nekomprimovaný SOAP požadavek, nebude komprimována ani SOAP odpověď. Třídu ZipUtilities by by možné udělat zcela bezstavovou (bez vlastností) a realizovat ji pomocí návrhového vzoru Singleton. To už jsou ale cvičení vhodná pro zaujatého čtenáře.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *