aspnet_intern.exe

É comum termos aplicações ASP.NET que fazem uso de uma mesma DLL, como por exemplo, NHibernate, log4Net, etc., e além destas, há também aplicações que consomem uma DLL que é criada para uso exclusivo, para uma empresa reutilizar alguns recursos em diversas de suas aplicações.

Quando há diversas aplicações que consomem a mesma DLL, ela deve estar fisicamente em todos os diretórios (bin) de suas respectivas aplicações, obrigando o sistema operacional a ler, carregar e manter separadamente cada uma dessas DLLs, ocupando memória RAM desnecessária, afinal, trata-se exatamente da mesma DLL para todas as aplicações.

O utilitário, tema deste post, tem a finalidade de diminuir a quantidade de RAM que é utilizada, centralizando as DLLs que são consumidas por várias aplicações em um local, e uma vez que ela está carregada, todas as aplicações fazem uso dela, sem a necessidade de cada aplicação fazer o mesmo (carregamento) para si, mantendo em memória uma única DLL. Fisicamente falando, esta DLL também deverá estar separada dos diretórios virtuais das aplicações, ou seja, cada site ainda manterá a sua própria cópia da DLL localmente (diretório bin), mas ao rodar o utilitário, vamos copiar estas DLLs que são candidatas à serem compartilhadas para um outro diretório a nossa escolha.

aspnet_intern.exe
-mode exec
-sourcedir “C:WindowsMicrosoft.NETFramework64v4.0.30319Temporary ASP.NET Files”
-interndir C:Common

Local: C:Program Files(86)Microsoft SDKsWindowsv8.1AbinNETFX 4.5.1 Tools

Ao rodar este utilitário ele sai em busca de DLLs que estão sendo utilizadas por, no mínimo, três aplicações e separa todas elas no diretório especificado no atributo -interndir. Mas é importante dizer que esse número mínimo pode ser configurado através do atributo -minrefcount. Ao rodar esse utilitário ele faz uma varredura, sugere e/ou adiciona as DLLs no diretório compartilhado, e inclui nas aplicações (dentro do diretório Temporary ASP.NET Files) o link para esta DLL, agora, compartilhada.

É importante dizer que na medida em que novas aplicações vão sendo instaladas neste servidor, pode ser com que elas façam uso das mesmas DLLs já compartilhadas ou trazerem novas DLLs, mas não farão uso daquelas que já estão compartilhadas. Para evitar isso e garantir a reutilização, basta rodar este utilitário periodicamente ou, no mínimo, quando novas aplicações forem instaladas no servidor web.

Particionando o Resultado no ASP.NET Web API

Via de regra, quando fazemos a requisição para algum recurso, ele retorna todo o conteúdo para cliente que solicitou. Enquanto estamos considerando simples porções de dados, não há muito problema em sempre retornar toda a informação sempre que solicitada. O problema pode ocorrer quando estamos retornando uma quantidade considerável de informação em resposta a requisição solicitada. Se durante a transmissão desta grande quantidade de informação a conexão, por algum motivo, for abortada, toda a informação se perde e será necessário recomeçar.

O protocolo HTTP contempla uma funcionalidade chamada de byte serving, que é um processo que faz o particionamento do resultado, enviando uma pequena porção de informação para o cliente, que por sua vez, pode controlar exatamente o que está sendo recebido, e com isso, poderá solicitar ao serviço a parte que ele ainda não tenha, evitando recomeçar do zero. Para formalizar esse tipo de comunicação, o protocolo HTTP faz uso de três headers: Range (requisições), Accept-Ranges e Content-Range (respostas).

Para exemplificar, vamos considerar que queremos fazer uma requisição para uma API que retorna o conteúdo de um arquivo texto, que possui a seguinte informação: 11223344556677889900. Quando o cliente realizar a requisição, ele deverá informar através do header Range a quantidade (em bytes) que ele quer que seja retornado. Ao atender a requisição, o serviço deverá ser capaz de capturar o arquivo em questão e particionar o conteúdo, para servir ao cliente somente o trecho que ele está solicitando. O serviço deverá responder com o status 206 (Partial Content), incluindo o header Content-Range, onde teremos o intervalo (dos dados) que está sendo disponibilizado e também a quantidade total que o conteúdo (o arquivo) possui. Vale lembrar que a propriedade Content-Length sempre refletirá a quantidade de dados que está sendo devolvido no corpo da mensagem e nunca o tamanho total do recurso (do arquivo) que está sendo utilizado.

O header Range deve definir a unidade e o intervalo que será utilizada para calcular a porção de informação que será devolvida. Note nos logs abaixo que está a requisição e sua respectiva resposta. A requisição sempre define o intervalo, e a resposta ratifica o intervalo extraído, contemplando também o tamanho total, para que o cliente possa criar o mecanismo de extração enquanto não recepcionar o conteúdo na íntegra, podendo inclusive criar mecanismos para pausar o download. O que também chama atenção é na última requisição, que apenas colocamos 13- para identificar que queremos o intervalo entre o byte 13 até o final do conteúdo.

—————————————————–

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=0-7

HTTP/1.1 206 Partial Content
Content-Length: 8
Content-Type: text/txt
Content-Range: bytes 0-7/23

11223

—————————————————–

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=8-12

HTTP/1.1 206 Partial Content
Content-Length: 5
Content-Type: text/txt
Content-Range: bytes 8-12/23

34455

—————————————————–

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=13-

HTTP/1.1 206 Partial Content
Content-Length: 10
Content-Type: text/txt
Content-Range: bytes 13-22/23

6677889900

—————————————————–

A implementação por parte da API não é tão complexa. Basta recorrermos à classe ByteRangeStreamContent, que faz toda a mágica para particionar e devolver somente o que foi solicitado. Obviamente que esta classe recebe como parâmetro um Stream contendo o conteúdo (que pode ser uma imagem, um arquivo texto, etc.) e o intervalo solicitado que pode (e deve) ser extraído da requisição, mas não há necessidade de realizar qualquer cálculo manual, pois de posse do header Range, ela será capaz de realizar todo o procedimento.

public class FilesController : ApiController
{
    [HttpGet]
    public HttpResponseMessage Download()
    {
        return new HttpResponseMessage(HttpStatusCode.PartialContent)
        {
            Content = new ByteRangeStreamContent(
                new FileStream(“Dados.txt”, FileMode.Open, FileAccess.Read),
                    this.Request.Headers.Range,
                    “text/txt”)
        };
    }
}

Processamento em Batch no ASP.NET Web API

Quando trabalhamos com serviços baseados em HTTP, ele são baseados em um modelo de requisição e resposta, ou seja, para cada solicitação que fazemos ao serviço, ele retorna uma resposta contendo o status ou o resultado da sua solicitação. E ainda falando especificamente sobre o protocolo HTTP, a cada mensagem trocada (na requisição ou na resposta), ela contém um cabeçalho e o corpo da mensagem. O cabeçalho é apenas um dicionário de chave e valor, com informações contextuais e o corpo da mensagem é opcional e pode trazer dados que estão sendo postados ou retornados, serializados em um formato específico.

Independentemente do que esteja dentro da requisição ou da resposta, tudo isso é computado como dados que estão sendo trafegados entre o cliente o serviço, e quando estamos em um ambiente onde o custo da rede é significativo (tanto em termos de performance quanto de custo (se estivermos falando em plano de dados de alguma operadora celular)) podemos ter surpresas durante o funcionamento da aplicação que o consome.

Para tentar reduzir isso, a Microsoft implementou no ASP.NET Web API 2 a possibilidade de realizar requisições em batch. A finalidade desta funcionalidade é basicamente permitir ao cliente empacotar múltiplas requisições em apenas uma, enviar ao serviço que o receberá e entregará as várias requisições para suas respectivas ações (métodos). Depois de processado, o resultado que é devolvido ao cliente também será um outro pacote, contendo o resultado para cada uma das requisições enviadas e processadas. Ambas bibliotecas do ASP.NET Web API (cliente e serviço) já estão preparadas para interagir com este tipo de serviço.

Para exemplificar, vamos utilizar uma API que já está disponível ao criar um projeto ASP.NET Web API: ValuesController. Vou omitir a implementação por questões de espaço, mas o que cada método faz é manipular a coleção estática chamada dados que está declarada no interior desta classe.

public class ValuesController : ApiController
{
    private static List<string> dados = new List<string>();

    public IEnumerable<string> Get() { }

    public string Get(int id) { }

    public void Post([FromBody]string value) { }

    public void Put(int id, [FromBody]string value) { }

    public void Delete(int id) { }
}

O primeiro passo é configurar o serviço para que ele aceite requisições em batch. Tudo começa com a inclusão de um rota que receberá este tipo especial de requisição. E para isso vamos recorrer ao método MapHttpBatchRoute, definindo como handler o DefaultHttpBatchHandler. É este handler, que recebe como parâmetro a instância do HttpServer que ele utilizará para tratar e processar as mensagens internamente, gerando os respectivos resultados e devolvendo para o cliente.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpBatchRoute(
            “BatchApi”,
            “api/batch”,
            new DefaultHttpBatchHandler(GlobalConfiguration.DefaultServer));

        config.Routes.MapHttpRoute(
            name: “DefaultApi”,
            routeTemplate: “api/{controller}/{id}”,
            defaults: new { id = RouteParameter.Optional });
    }
}

Uma preocupação que é sempre levantanda com tudo que é processado em batch, é a sequência em que isso é feito. Por padrão, o DefaultHttpBatchHandler utiliza o modelo sequencial, que como o prório nome sugere, executa as requisições na mesma ordem em que elas foram empacotadas, sendo que ele somente processará a requisições seguinte depois que a anterior finalizar. Caso a ordem não seja relevante para o processamento das requisições, então podemos configurar o handler para realizar o processamento de forma não sequencial e, consequentemente, de forma assíncrona. Para isso, basta recorrer à propriedade ExecutionOrder, escolhendo uma das opções expostas pelo enumerador BatchExecutionOrder:

config.Routes.MapHttpBatchRoute(
    “BatchApi”,
    “api/batch”,
    new DefaultHttpBatchHandler(GlobalConfiguration.DefaultServer)
    {
        ExecutionOrder = BatchExecutionOrder.NonSequential
    });

Com isso o serviço está pronto para receber requisições em batch. Agora compente aos clientes também se ajustarem para conseguir enviar e receber requisições neste formato. Como já foi mencionado acima, a biblioteca do ASP.NET Web API para consumo de serviços HTTP já possui suporte para isso. A implementação deste recurso foi baseada no tipo de conteúdo conhecido como multipart, que é quando um ou mais conjuntos de dados são combinados em um único body. Assim como uma mensagem HTTP é composta por um cabeçalho, uma linha em branco como separador e um corpo, cada conjunto de dados do multipart (denominado body parts) tem um cabeçalho, uma linha em branco e um corpo. Cada uma dessas partes também possui uma espécie de chave (boundary) que é utilizado para associar essas partes.

O uso por parte do cliente consiste instanciar a classe MultipartContent e adicionar nela as partes, que neste caso são requisições para um mesmo serviço/API. A classe HttpMessageContent recebe em seu construtor a instância da classe HttpRequestMessage, que como sabemos, representa uma requisição HTTP, e devemos realizar a configuração dela como se estivéssemos fazendo a configuração para submete-la diretamente para o HttpClient, mas não é o caso.

private static async Task Run()
{
    using (var client = new HttpClient())
    {
        var requests = 
            new MultipartContent(“mixed”, “batch_” + Guid.NewGuid().ToString());

        requests.Add(
            new HttpMessageContent(
                new HttpRequestMessage(HttpMethod.Post, “http://localhost:4467/api/values&#8221;)
        {
            Content = new ObjectContent<string>(“Israel”, new JsonMediaTypeFormatter())
        }));

        requests.Add(
            new HttpMessageContent(
                new HttpRequestMessage(HttpMethod.Get, “http://localhost:4467/api/values&#8221;)));

        using (var br = await client.SendAsync(
            new HttpRequestMessage(HttpMethod.Post, “http://localhost:4467/api/batch&#8221;)
            {
                Content = requests
            }))
        {
            var responses = await br.Content.ReadAsMultipartAsync();
            var postResponse = await responses.Contents[0].ReadAsHttpResponseMessageAsync();
            var getResponse = await responses.Contents[1].ReadAsHttpResponseMessageAsync();

            foreach (var item in await getResponse.Content.ReadAsAsync<IEnumerable<string>>())
                Console.WriteLine(item);
        }
    }
}

É importante notar que uma das mensagens está postando o nome “Israel” para que seja adicionado à coleção, e logo na sequência, estamos empacotando também a chamada para o método Get, que deverá retornar todos os nomes adicionados. Finalmente, depois das requisições que desejamos realizar empacotadas no MultipartContent, então criamos uma terceira requisição (HttpRequestMessage) que levará em seu corpo as requisições POST e GET que configuramos e, como já era de se esperar, esta terceira requisição deve ser direcionada para o endpoint de batch que configuramos no serviço, que saberá como tratar esse tipo de mensagem.

Depois da requisição realizada pelo HttpClient e devolvida pelo serviço, podemos capturar através do método ReadAsMultipartAsync o conteúdo (resposta) da requisição que foi realizada para o endpoint de batch. Da mesma forma que a requisição, a resposta desta requisição também contém em seu interior a resposta para cada um métodos que foram empacotados, e justamente por isso, devemos recorrer à coleção chamada Contents, que através do índice podemos extrair cada uma das respostas que desejarmos.

Com toda essa configuração realizada, se interceptarmos a requisição e resposta podemos visualizar todo o trabalho sendo realizado pelo cliente e pelo serviço, e através das mensagens HTTP que foram trocadas, podemos confirmar todo o procedimento que está sendo realizado para garantir o envio e recebimento correto pelas partes.

[Requisição]

POST http://localhost:4467/api/batch HTTP/1.1
Content-Type: multipart/mixed; boundary=”batch_93565607-7bd6-4147-b2eb-27a6b35cd42a”
Host: localhost:4467
Content-Length: 402
Expect: 100-continue
Connection: Keep-Alive

–batch_93565607-7bd6-4147-b2eb-27a6b35cd42a
Content-Type: application/http; msgtype=request

POST /api/values HTTP/1.1
Host: localhost:4467
Content-Type: application/json; charset=utf-8

“Israel”
–batch_93565607-7bd6-4147-b2eb-27a6b35cd42a
Content-Type: application/http; msgtype=request

GET /api/values HTTP/1.1
Host: localhost:4467

–batch_93565607-7bd6-4147-b2eb-27a6b35cd42a–

[Resposta]

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 360
Content-Type: multipart/mixed; boundary=”70d3b7ff-6b4c-41ca-aa72-3f2225c19344″
Expires: -1
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 20 Nov 2013 12:20:52 GMT

–70d3b7ff-6b4c-41ca-aa72-3f2225c19344
Content-Type: application/http; msgtype=response

HTTP/1.1 204 No Content

–70d3b7ff-6b4c-41ca-aa72-3f2225c19344
Content-Type: application/http; msgtype=response

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[“Israel”,”Israel”,”Israel”,”Israel”]
–70d3b7ff-6b4c-41ca-aa72-3f2225c19344–