Sonntag, November 02, 2008

Compression over WCF-Transports

Ich habe mich die letzten Wochen/-enden mit Komprimierung von WCF-Daten beschäftigt. Komprimierung könnte gerade bei der Anbindung von vielen Clients im WAN Bereich positive Ergebnisse erzielen. Im lokalen Bereich schadet es bei heutigen Prozessoren sicherlich wenig und für große Datenmengen ist es in diesen Fällen ideal.

Ich war allerdings nicht so verrückt mich auf eine Neuentwicklung einzulassen. Es musste so etwas bereits geben und so war es auch. In den WCF-Samples ist ein Beispiel enthalten, dies soll vor allem das erstellen von eigenen WCF-Transports demonstrieren. Mit der Lösung habe ich etwas herum gespielt, allerdings habe ich nur eine http-Transport damit zum laufen bekommen. Auf der Suche nach alternativen bin ich auf das CodePlex-Project WCF-Extensions gestoßen. Das Projekt hat bisher kein Release veröffentlicht, allerdings kann man den Code downloaden.

Statt den Code auszuprobieren, habe ich erst mal den Code erweitert. Ich habe zusätzlichen Komprimierungsalgorithmus hinzugefügt, so dass es nun ganze 5 Möglichkeiten gibt.

   1:  public enum CompressionAlgorithm
   2:  {
   3:      GZip,
   4:      Deflate,
   5:      BZip2,
   6:      Zip,
   7:      GZip2
   8:  }

Die letzten 3 Algorithmen verwenden die SharpZibLib zur Komprimierung. Ich finde die ZibLib eine super Implementierung, wer es noch ausgefallener/besser möchte kann dies auch noch um Xceed Zip for .Net aufbohren.

Nach den Anpassungen habe ich zumindest mal einige Tests mit dem integrierten Client gemacht. Es hat sich schnell gezeigt, dass geringe Datenmengen, < 200 Bytes, durch die Komprimierung sogar minimal vergrößert wurden. Die besten Ergebnisse habe ich immer mit BZip2 erhalten. Einen genauen Vergleich der Komprimierungsraten und der Verhalten im WCF-Streams habe ich nicht bis zu Ende durchgeführt. Mir hat es gereicht, nach dem es funktionierte.

So, nun aber zum schwierigeren Teil, die Konfiguration in der app.config. Ich hatte mich eine ganze Weile durch verschiedene MSDN-Pages gesucht und die Konfiguration zusammen zu frickeln. Ich bin froh, dass beim Startup der Anwendung die Konfiguration geprüft wird. Meine erste Konfiguration sah wie folgt aus:

   1:    <system.serviceModel>
   2:      <extensions>
   3:        <bindingElementExtensions>
   4:          <add name="compression" type="WcfExtensions.ServiceModel.Configuration.CompressionElement, WcfExtensions.ServiceModel"/>
   5:        </bindingElementExtensions>
   6:      </extensions>
   7:      <behaviors>
   8:        <serviceBehaviors>
   9:          <behavior name="CustomerService">
  10:            <serviceDebug httpHelpPageUrl="http://localhost:55557/Provisioning/CustomerService"
  11:              includeExceptionDetailInFaults="true" />
  12:            <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:55556/Provisioning/CustomerService" />
  13:            <serviceTimeouts transactionTimeout="00:00:40" />
  14:          </behavior>
  15:        </serviceBehaviors>
  16:      </behaviors>
  17:      <bindings>
  18:        <customBinding>
  19:          <binding name="tcpCompressed" openTimeout="00:00:20" receiveTimeout="00:00:40"
  20:            sendTimeout="00:01:30">
  21:            <compression algorithm="BZip2" level="Normal"/>
  22:            <transactionFlow />
  23:            <binaryMessageEncoding maxReadPoolSize="32"
  24:                              maxWritePoolSize="32"
  25:                              maxSessionSize="4096">
  26:              <readerQuotas
  27:                  maxArrayLength="8000"
  28:                      maxBytesPerRead="4096"
  29:                      maxDepth="32"
  30:                      maxNameTableCharCount="16384"
  31:                      maxStringContentLength="65536" />
  32:            </binaryMessageEncoding>
  33:            <tcpTransport hostNameComparisonMode="WeakWildcard" transferMode="StreamedResponse"
  34:            maxReceivedMessageSize="1048576000">
  35:            </tcpTransport>
  36:          </binding>
  37:        </customBinding>
  38:      </bindings>
  39:      <services>
  40:        <service behaviorConfiguration="CustomerService" name="DE.CapeVision.Windows.Services.AgentService.Components.ProjectProvisioningWorkflow.CustomerService">
  41:          <endpoint address="net.tcp://localhost:20102/Provisioning/CustomerService"
  42:            binding="customBinding" bindingConfiguration="tcpCompressed"
  43:            contract="DE.CapeVision.Common.Contracts.Customers.ICustomerService" />
  44:        </service>
  45:      </services>
  46:    </system.serviceModel>

Es funktionierte mit der Konfiguration recht gut, allerdings kam irgendwann die Stelle, die ein weitersuchen erforderte, ich hatte keine Authentifizierung. Aber ich wollte unbedingt WindowsSecurity für die Anwendung haben. Es handelt sich um eine kleine Intranet-Anwendung. Authentifizierung ist ein wesentliches Merkmal zur minimalen Absicherung des Dienstes. Ein halber Tag und viele Kämpfe mit der MSDN Doku, habe ich es geschafft. Am Ende fehlten die beiden Elemente vor dem Transport (dick hervorgehoben 33-36), die Konfiguration sah so aus:

   1:    <system.serviceModel>
   2:      <extensions>
   3:        <bindingElementExtensions>
   4:          <add name="compression" type="WcfExtensions.ServiceModel.Configuration.CompressionElement, WcfExtensions.ServiceModel"/>
   5:        </bindingElementExtensions>
   6:      </extensions>
   7:      <behaviors>
   8:        <serviceBehaviors>
   9:          <behavior name="CustomerService">
  10:            <serviceDebug httpHelpPageUrl="http://localhost:55557/Provisioning/CustomerService"
  11:              includeExceptionDetailInFaults="true" />
  12:            <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:55556/Provisioning/CustomerService" />
  13:            <serviceTimeouts transactionTimeout="00:00:40" />
  14:          </behavior>
  15:        </serviceBehaviors>
  16:      </behaviors>
  17:      <bindings>
  18:        <customBinding>
  19:          <binding name="tcpCompressed" openTimeout="00:00:20" receiveTimeout="00:00:40"
  20:            sendTimeout="00:01:30">
  21:            <compression algorithm="BZip2" level="Normal"/>
  22:            <transactionFlow />
  23:            <binaryMessageEncoding maxReadPoolSize="32"
  24:                              maxWritePoolSize="32"
  25:                              maxSessionSize="4096">
  26:              <readerQuotas
  27:                  maxArrayLength="8000"
  28:                      maxBytesPerRead="4096"
  29:                      maxDepth="32"
  30:                      maxNameTableCharCount="16384"
  31:                      maxStringContentLength="65536" />
  32:            </binaryMessageEncoding>
  33:            <security authenticationMode="SspiNegotiatedOverTransport"
  34:                   requireSecurityContextCancellation="false">
  35:            </security>
  36:            <windowsStreamSecurity protectionLevel="EncryptAndSign"/>
  37:            <tcpTransport hostNameComparisonMode="WeakWildcard" transferMode="StreamedResponse"
  38:            maxReceivedMessageSize="1048576000">
  39:            </tcpTransport>
  40:          </binding>
  41:        </customBinding>
  42:      </bindings>
  43:      <services>
  44:        <service behaviorConfiguration="CustomerService" name="DE.CapeVision.Windows.Services.AgentService.Components.ProjectProvisioningWorkflow.CustomerService">
  45:          <endpoint address="net.tcp://localhost:20102/Provisioning/CustomerService"
  46:            binding="customBinding" bindingConfiguration="tcpCompressed"
  47:            contract="DE.CapeVision.Common.Contracts.Customers.ICustomerService" />
  48:        </service>
  49:      </services>
  50:    </system.serviceModel>

Die Clientseitige Konfiguration unterscheidet sich nur minimal von der gegebenen, es sollte sich schnell erstellen lassen. Hier der Vollständigkeit die Client-Seite:

   1:    <system.serviceModel>
   2:      <extensions>
   3:        <bindingElementExtensions>
   4:          <add name="compression" type="WcfExtensions.ServiceModel.Configuration.CompressionElement, WcfExtensions.ServiceModel"/>
   5:        </bindingElementExtensions>
   6:      </extensions>
   7:      <bindings>
   8:        <customBinding>
   9:          <binding name="tcpCompressed" openTimeout="00:00:20" receiveTimeout="00:00:40"
  10:            sendTimeout="00:01:30">
  11:            <compression algorithm="GZip2" level="Fast"/>
  12:            <transactionFlow />
  13:            <binaryMessageEncoding maxReadPoolSize="32"
  14:                              maxWritePoolSize="32"
  15:                              maxSessionSize="4096">
  16:              <readerQuotas
  17:                  maxArrayLength="8000"
  18:                      maxBytesPerRead="4096"
  19:                      maxDepth="32"
  20:                      maxNameTableCharCount="16384"
  21:                      maxStringContentLength="65536" />
  22:            </binaryMessageEncoding>
  23:            <security authenticationMode="SspiNegotiatedOverTransport"
  24:                   requireSecurityContextCancellation="false">
  25:            </security>
  26:            <windowsStreamSecurity protectionLevel="EncryptAndSign"/>
  27:            <tcpTransport hostNameComparisonMode="WeakWildcard" transferMode="StreamedResponse"
  28:            maxReceivedMessageSize="1048576000"/>
  29:          </binding>
  30:        </customBinding>
  31:      </bindings>
  32:      <client>
  33:        <endpoint address="net.tcp://localhost:20102/Provisioning/CustomerService" binding="customBinding" bindingConfiguration="tcpCompressed" contract="DE.CapeVision.Common.Contracts.Customers.ICustomerService" />
  34:      </client>
  35:    </system.serviceModel>

Hier natürlich noch der manipulierte Source Code der WCF-Extensions: Download. Um es produktiv einzusetzen muss allerdings noch etwas geschraubt werden, damit Performance Informationen abgegriffen werden können. Bis auf die 2 Trace-Ausgaben ist derzeit nichts enthalten.

UPDATE: Ich habe heute eine kleine Unschönheit gefixed, der Code in der Quelle ist entsprechen aktualisiert. Die Channels werden nicht sauber durch gereicht, so dass es unter Umständen zu einer Cast-Exception in der CompressionChannelBase kommt.

Technorati-Tags: ,,