Integrações entre Sistemas – Parte 5 – HTTP Request

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.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s