Objetivo
Nesta parte veremos mais uma abordagem para transferência de informações que é através de um request HTTP.
Setup
O servidor para nosso teste foi implementado como uma aplicação Web (IntegrationTests.WebApp), num handler http genérico. Para utilizá-lo, precisamos configurar nosso IIS. Para isso, seguiremos os passos:
- Abrir a console do IIS (inetmgr)
- Adicionar novo aplicativo, nome “integrationtests2”, Application Pool DefaultAppPool (v. 4.0, Integrated)
- Caminho físico: apontar para o diretório IntegrationTests\IntegrationTests.WebApp
- Configurar a connection string do banco de dados, no arquivo web.config existente no diretório IntegrationTests\IntegrationTests.WebApp
Código do teste
O teste segue a seguinte abordagem:
- O cliente executa um request http no servidor, baseado na stream do request, escreve o xml com a requisição para o servidor
- O servidor, recebe o request, enquanto vai escrevendo a resposta, vai dando “Flushes” no request, de forma que a stream de resposta é devolvida em pedaços para o cliente
- O cliente, vem lendo a stream de resposta enquanto o servidor ainda está processando o request.
Segue o fonte do servidor, escrito como um generic handler (.ashx) numa aplicação web padrão:
public class GenericHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (String.IsNullOrEmpty(connString))
GetConnString();
if (context.Request.Headers["Flush"] == "true")
flush = true;
context.Response.ContentType = "text/xml";
this.context = context;
StreamUtil u = new StreamUtil();
u.ProcessClientBigRequest(connString, context.Request.InputStream, context.Response.OutputStream, true, Flush);
context.Response.Flush();
context.Response.End();
}
private void Flush()
{
if (flush)
context.Response.Flush();
}
Mais uma implementação simplista. A idéia aqui é obter a connection string, posteriormente, ler o header pra saber se os flushs parciais serão dados ou não. O método Flush, é dado como um callback para ProcessClientBigRequest. No meio do loop dele, ele executa o callback que vem dando Response.Flush, para cada pedacinho de xml tratado.
O código do cliente (task MSBuild) segue:
public override bool Execute()
{
Stopwatch watch = new Stopwatch();
watch.Start();
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(Uri));
request.Method = "POST";
request.ContentType = "text/xml";
request.BeginGetRequestStream(GetRequestStreamCallback, request);
while (!finished)
Thread.Sleep(250);
watch.Stop();
Log.LogMessage("Total processing time: " + watch.Elapsed.TotalSeconds.ToString("0.00") + " seconds");
return true;
}
private void GetRequestStreamCallback(IAsyncResult result)
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
if (Flush)
request.Headers.Add("Flush:true");
Stream requestStream = request.EndGetRequestStream(result);
Log.LogMessage("Writing request with " + BigRequestSize.ToString() + " itens. Flush " + Flush.ToString());
StreamUtil util = new StreamUtil();
util.GenerateBigRequest(requestStream, true, BigRequestSize);
request.BeginGetResponse(GetResponseCallback, request);
}
private void GetResponseCallback(IAsyncResult result)
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
Stream responseStream = response.GetResponseStream();
Log.LogMessage("Reading response");
_util = new StreamUtil();
_util.ImportarStream(ConnString, responseStream);
finished = true;
}
O método Execute dispara o request (usando BeginGetRequestStream) e fica aguardando o retorno. O callback do BeginGetRequestStream é o método GetRequestStreamCallback. Este método adiciona os headers, e escreve na stream de request o xml com o request, item a item. Em seguida, executa o método BeginGetResponse, que por sua vez chama o callback GetResponseCallback.
O método GetResponseCallback, interpreta a stream contida na resposta, inserindo os resultados na base.
Conclusão
A idéia inicial que eu tinha ao construir este teste era vir devolvendo o response enquanto recebia o request. Percebi que por uma característica do protocolo http, isso é impossível. Não é possível começar a escrever a resposta enquanto o request ainda está sendo lido. Isso faz com que ocorra uma bufferização do request do lado do servidor, ou seja, como ele precisa esperar chegar todo o request antes de começar a gerar a resposta, ele vai alocando memória para esta stream (no lado do servidor), até que comece a paginação. A partir do momento que ele começa a liberar a resposta, esta memória vai sendo liberada.
Apesar de muito eficiente, ao executarmos este método com um request grande, percebemos um consumo alto de memória. É um método relativamente simples de transferir informações, tem esse ponto de atenção da memória e mesmo assim, não aproveita muito bem os recursos das duas máquinas, visto que acaba serializando o processamento (precisa esperar todo o request para começar a gerar a resposta). O único ponto que ele torna mais eficiente é que à medida que a resposta vai sendo escrita, o cliente já vai processando ela.
Código fonte da solução
O código fonte está disponível no git hub: https://github.com/ericlemes/IntegrationTests.