Archivio mensile:ottobre 2011

Comprimere il contenuto di una risposta web, e analisi dell’Accept-Encoding

Il seguente articolo, è inerente c# e in particolare all’integrazione di un CompressionFilter nel  workflow di una chiamata web mediante il framework MVC2 di Microsoft, ma è applicabile in linea di massima a tutti i linguaggi. Si tratta fondamentalmente di applicare un algoritmo di compressione ai dati in output da una chiamata http, in modo da far viaggiare meno dati sulla rete, e accertarsi di quale metodi di compressione il client chiamante supporti.

Ci sono diversi tutorial sulla rete riguardo a questa problematica, ma è difficile trovare una risposta assoluta in quanto il vero problema non è la creazione di una funzione per la compressione (già implementate nei vari framework), ma nella decodifica corretta dell’Header Accept-Encoding che ci aiuta a dedurre che cosa supporti il chiamante, evitando così di applicare meccanismi di compressione non graditi o non supportati.

Banalmente basterebbe controllare se nell’header in questione sia presente la stringa gzip o deflate ,ad indicare che il client supporta i due algoritmi di compressione standard, ma in realtà le specifiche W3C sull’encoding, prevedono un funzionamento molto piu complesso.

 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

Questo tipo di Headers si basano su un concetto di preferenza e pesi..

Alcuni esempi di valori possibili:

  • il più banale “gzip” oppure solo “deflate” ad indicare l’algoritmo desiderato.
  • “gzip,deflate” ad indicare la sequenza delle preferenze,
  • “gzip;q=0,deflate” ad indicare che gzip non dovrebbe minimamente essere usato (ha un peso Q pari a zero)
  • “gzip;q=0.4,deflate;q=0.4,identity;q=0.2” ad indicare la ripartizione tra le preferenze..
  • identity sta a indicare che non va assolutamente usato un content-encoding.. se espresso senza q, il default sui parametri e’ q=1 e quindi avrebbe sempre la precedenda come in questo esempio: “gzip;q=0.5,deflate;q=0.5,identity”..
  • “*” sta invece a indicare qualsiasi encoding

Da quello che si evince e’ impossibile andare a cercare semplicemente se nella stringa sia presente gzip o deflate.. perche si rischia di incappare in errori (come da esempio con lo “*” o con identity dove q=1)

Sulla rete si trovano gia delle List per valori pesati oppure realizziamo noi direttamente una semplice classe che tokenizza la stringa usando la virgola come separatore e poi tokenizza ogni token utlizzando il “;” . Ovviamente in caso di “*” la scelta di default possiamo attribuirla noi ad esempio gzip. Il sort dei token e’ eseguito sulla base del valore di q, andando a mettere q=1 a tutti gli elementi con q non specificato.

Poi passiamo alla parte MVC e realizziamo un ActionAttributeFilter che utilizzeremo per annotare le Action nel nostro Controller , che saranno sottoposte a compressione.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System;

using System.Collections.Generic;

using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Collections.Specialized;
using System.IO.Compression;
using System.IO;

public class CompressionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{

HttpRequestBase request = filterContext.HttpContext.Request;
string acceptEncodingHeader = request.Headers["Accept-Encoding"];
if (acceptEncodingHeader == null) return;

HttpResponseBase response = filterContext.HttpContext.Response;

MyFantasticQList encodingPreference = new MyFantasticQList(acceptEncodingHeader);

encodingPreference.setDefaultPreference("gzip");

string preferred = encodingPreference.findPreferred();

switch(preferred.Name){

case "gzip":

Response.AppendHeader("Content-Encoding""gzip");

Response.Filter = new GZipStream(Response.Filter, CompressionMode.Compress);

break;

case "deflate":

Response.AppendHeader("Content-Encoding""deflate");

Response.Filter = new DeflateStream(Response.Filter,CompressionMode.Compress);

break;

default:

break;

}

}

Realizzato il nostro CompressFilterAttribute, nel nostro controller basta annotare le action che devono restituire contenuto compresso:

 

1
2
3
4
5
6
7
8
9
[HttpPost()]

[CompressionFilter]

public ActionResult ListaElementi(string key){

//codice dell'action

}

In automatico prima della response, verrà applicato il content encoding corretto .

Ciao.

 

Ottenere il Soap Message in WCF

A volte cose molto semplici richiedono delle operazioni aggiuntive per essere espletate, e quindi cio che ci aspettiamo essere una semplice Get, prevede invece l’utilizzo di diversi pattern.

Il nostro caso:

stiamo utilizzando un client WCF per chiamare un webservice e vorremmo ottenere programmaticamente, quindi da codice, la busta Soap per memorizzarla ad esempio in sistemi di monitoring o debug particolari.

La soluzione:

Realizziamo una classe che implementi due interfacce, IClientMessageInspector e IEndpointBehavior.

Implementiamo le interfacce e otteniamo:

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
31
32
33
34
public class MessageInspector : IClientMessageInspector, IEndpointBehavior

{
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
//throw new NotImplementedException();
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
Console.WriteLine("Soap Message:"+request.toString());
return null;
}

public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
//;
}

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(this);
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
//;
}

public void Validate(ServiceEndpoint endpoint)
{
//;
}

}

Nel metodo BeforeSendRequest otteniamo il messaggio prima dell’invio e ad esempio lo stampiamo sulla console..

Per poter poi aggiungere questo Inspector alle caratteristiche del nostro client, basta passarlo in questo modo:

1
2
//serviceClient e' l'istanza del nostro client WCF
serviceClient.ChannelFactory.Endpoint.Behaviors.Add(new MessageInspector());

Importante l’implementazione del metodo ApplyClientBehavior , dall’interfaccia IEndpointBehavior, che ci permette di aggiungere il nostro Inspector agli Inspector del client WCF. Come tutto in WCF, anche gli inspector sono completamente configurabili nell’app.config (o web.config), nella parte di serviceModel e tramite delle extensions.

Spero di avervi (e in particolare a chi me lo ha chiesto) aiutato.

Consumare Wcf con configurazione programmatica (senza toccare il web.config)

Le Windows Communication Foundation permettono una configurazione molto semplice e efficace, risolvendo in automatico i binding e tutte le configurazioni di channel, porte e quanto altro direttamente leggendo da web.config oppure da un app.config.

Il problema nasce quando andiamo a utilizzare ad esempio un client verso un servizio wcf , direttamente in delle application pages o webpart all’interno di Sharepoint.

In questo caso la configurazione può essere gestita in 3 modi:

Nel primo caso dobbiamo mettere mano al web.config di Sharepoint, in quanto le application pages e/o le webpart sono hostate all’interno della sua Web Applicaiton, e questo per motivi di policy, non sempre è possibile.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<system.serviceModel>

<bindings>

<basicHttpBinding>

<binding name="RichiestaDatiSoapBinding" closeTimeout="00:01:00"

openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"

maxBufferSize="2147483647" maxBufferPoolSize="524288" maxReceivedMessageSize="2147483647"

messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"

useDefaultWebProxy="true">

<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16384"

maxBytesPerRead="4096" maxNameTableCharCount="16384" />

<security mode="None">

<transport clientCredentialType="None" proxyCredentialType="None"

realm="" />

<message clientCredentialType="UserName" algorithmSuite="Default" />

</security>

</binding>

</basicHttpBinding>

</bindings>

<client>

<endpoint address="http://localhost:81/testservice/servizio.asmx" binding="basicHttpBinding" bindingConfiguration="RichiestaDatiSoapBinding"

contract="TestService.RichiestaDatiSoapBinding" name="RichiestaDatiSoapBinding" />

</client>

</system.serviceModel>

Il secondo meccanismo , messo a disposizione direttamente dalle Api di Sharepoint, prevede l’utilizzo delle SPWebConfigModification Api, che permettonod i manipolare il web.config di iis, mediante codice, andando ad aggiungere le parti che servono in fase , ad esempio, di attivazione di una feature.. A mio avviso questa parte è qualcosa di veramente terrificante, perche si arriva ad ottenere un web.config spesso illeggibile e quasi sempre non funzionante e oltretutto difficile da controllare visto che tutto il value che deve essere passato al configModification deve avere l’escape di tutti i caratteri particolari.. quindi pieno di entity xml come il @quot …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SPWebConfigModification modificaWebConfig= new SPWebConfigModification();

modificaWebConfig.Path = "configuration/system.serviceModel";

modificaWebConfig.Sequence = 0;

modificaWebConfig.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;

modificaWebConfig.Owner = System.Threading.Thread.CurrentPrincipal.Identity.Name;

modificaWebConfig.Value = @"<bindings><basicHttpBinding>..etc.etc..inserire qui tutto l'xml del binding";

SPWebService service = SPWebService.ContentService;

service.WebConfigModifications.Add(modificaWebConfig);
service.Update();
service.ApplyWebConfigModifications();

Un terzo modo è quello di creare una classe che fornisca direttamente il Binding (BasicHttpBinding) e l’Endpoint (EndPointAddess).

Purtroppo per motivi di sicurezza , microsoft ha bloccato la possibilità di leggere l’xml e deserializzarlo nelle relative classi messe a disposizioni dal Framework, quindi la creazione del binding va fatta “a mano”.

I dati possono essere statiticizzati nel codice o ad esempio letti da una Lista di Sharepoint, in cui magari andiamo a memorizzare in un unico Item tutto il blocco xml della configurazione del binding .

 

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class WCFClientConfiguration
{

public BasicHttpBinding Binding
{
get
{
BasicHttpBinding _binding = new BasicHttpBinding()
{
Name = "ServizioTestSoap",
CloseTimeout = new TimeSpan(1, 5, 0),
OpenTimeout = new TimeSpan(1, 5, 0),
ReceiveTimeout = new TimeSpan(1, 10, 0),
SendTimeout = new TimeSpan(1, 5, 0),
AllowCookies = false,
BypassProxyOnLocal = true,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferSize = 600000,
MaxBufferPoolSize = 524288,
MaxReceivedMessageSize = 600000,
MessageEncoding = WSMessageEncoding.Text,
TextEncoding = System.Text.Encoding.UTF8,
TransferMode = TransferMode.Buffered,
UseDefaultWebProxy = false,
ReaderQuotas = new XmlDictionaryReaderQuotas()
{
MaxDepth = 32,
MaxStringContentLength = 500000,
MaxArrayLength = 16384,
MaxBytesPerRead = 4096,
MaxNameTableCharCount = 16384
}

};

_binding.Security.Mode = BasicHttpSecurityMode.Transport;

return _binding;
}
}

public EndpointAddress Endpoint
{
get
{
return new EndpointAddress("https://localhost:81/testService/service.asmx");
}
}
}

 

In questo modo quando andiamo a instanziare il nostro client, usiamo il costruttore che accetta in input Binding e Endpoint.

1
2
3
WcfConfiguration conf = new WCFClientConfiguration();

ServizioTestSoapClient client = new ServizioTestSoapClient (conf.Binding,conf.Endpoint);

Questo permette la memorizzazione ad esempio della configurazione su DB o su altri sistemi di persistenza .