DClick

Enviando várias imagens da aplicação Flex para o servidor, mas todas de uma vez!


Olá pessoal esse é o nosso primeiro post aqui no Blog da DClick (meu e do Leonardo Cabral), e espero que seja o primeiro de muitos. Sem mais "bla bla bla", vamos cair no assunto que é o que interessa.

A necessidade de enviar diversas imagens para o servidor surgiu em uma tela que gera até 4 gráficos e existe uma opção de imprimir esses gráficos. Os gráficos gerados na aplicação Flex seriam anexados a um relatório com cabeçalho e rodapé já definidos e um arquivo PDF seria exportado.

A captura da imagem foi implementada facilmente com o componente "Snapshot", que já havia sido desenvolvido pela DClick. O problema agora era enviar as imagens capturadas para o servidor. Como o Flex só permite realizar uploads de arquivos (usando o FileReference) que são explicitamente selecionados pelo usuário (por motivos de segurança), e também não permite a realização de múltiplos uploads de uma vez, ficávamos com a opção de enviar um arquivo após o outro através do parâmetro "data" do Objeto "URLRequest". Essa solução tinha um problema, seria necessário gravar as imagens em disco no servidor (ou em uma variável de sessão, "entulhando" a sessão), exportar o PDF (que não é gerado em disco, mas em memória) e remover as imagens do servidor após download ou visualização do relatório pelo usuário.

Realizamos algumas pesquisas na internet, mas sem muito sucesso. Quando o nosso amigo Rafael Cordeiro (o cara que tem no menu favoritos do browser todos os links que você já precisou ou irá precisar algum dia) nos trouxe um link com informações e códigos sobre como enviar um ByteArray para o servidor. Real file upload with AS3. O problema é esse código só envia um arquivo por vez. Ele utiliza a mesma forma que a classe "UploadPostHelper", desenvolvida por Jonathan Marston, que o Beck postou no "Salvando imagens da sua aplicação Flex no computador do Usuário". Precisávamos mais que isso. Sabíamos que era possível, pois em um formulário HTML comum todos os INPUTS do tipo "file", que possuem arquivos selecionados, são enviados ao servidor de uma vez. Estudamos a forma que o "URLRequest" cria o cabeçalho do Request que é enviado ao servidor e desenvolvemos uma classe que envia vários ByteArrays ao servidor através de um único Request. Essa classe escreve os ByteArrays na propriedade "data" do objeto "URLRequest", da mesma forma que os posts normais são gerados. Como o Beck disse no post: “Feito isso a parte Java fica fácil.”. Não há mistério pois os arquivos chegam através de um Request comum. Também é possível enviar parâmetros por query string junto com os arquivos.

Segue abaixo o código-fonte da classe "UploadFiles":

Actionscript:
  1. package
  2. {
  3.      import flash.net.URLLoader;
  4.      import flash.net.URLLoaderDataFormat;
  5.      import flash.net.URLRequest;
  6.      import flash.net.URLRequestHeader;
  7.      import flash.net.URLRequestMethod;
  8.      import flash.utils.ByteArray;
  9.      import flash.utils.Endian;
  10.  
  11.      import mx.collections.ArrayCollection;
  12.      import flash.net.navigateToURL;
  13.  
  14.      public class UploadFiles
  15.      {
  16.  
  17.           public static function uploadFiles(url: String, files:ArrayCollection = null, parametros:ArrayCollection = null, loader:URLLoader = null):void
  18.           {
  19.                if (files == null && parametros == null) {
  20.                     throw new Error("Nenhum arquivo ou parâmetro foi passado para o método 'uploadFiles'.");
  21.                }
  22.    
  23.                var boundary: String = gerarBoundary();
  24.                var postData: ByteArray = new ByteArray;
  25.  
  26.                postData.endian = Endian.BIG_ENDIAN;
  27.  
  28.                //Gerando request para cada arquivo que será enviado
  29.                if (files != null)
  30.                {
  31.  
  32.                     for (var i:int = 0; i <files.length; i++)
  33.                     {
  34.  
  35.                          var file:File = files.getItemAt(i) as File;
  36.  
  37.                          // -- + boundary
  38.                          insertStringInByteArray("--" + boundary, postData);
  39.  
  40.                          // Quebra de linha
  41.                          insertLineBreakInByteArray(postData);
  42.  
  43.                          // Content Disposition
  44.                          insertStringInByteArray('Content-Disposition: form-data; name="' + file.fileName + '"; filename="' + file.fileName + '"', postData);
  45.  
  46.                          // Quebra de linha
  47.                          insertLineBreakInByteArray(postData);
  48.  
  49.                          // Content Type
  50.                          insertStringInByteArray("Content-Type: " + file.contentType, postData);
  51.  
  52.                          // 2 Quebras de linha
  53.                          insertLineBreakInByteArray(postData);
  54.                          insertLineBreakInByteArray(postData);
  55.  
  56.                          // Conteúdo do arquivo que será enviado
  57.                          postData.writeBytes(file.byteFile, 0, file.byteFile.length);
  58.  
  59.                          // Quebra de linha
  60.                          insertLineBreakInByteArray(postData);
  61.  
  62.                     }
  63.  
  64.                }
  65.  
  66.                //Gerando request para cada parametro que será enviado
  67.                if (parametros != null)
  68.                {
  69.  
  70.                     for (i = 0; i <parametros.length; i++)
  71.                     {
  72.  
  73.                          var parametro:Parametro = parametros.getItemAt(i) as Parametro;
  74.  
  75.                          // -- + boundary
  76.                          insertStringInByteArray("--" + boundary, postData);
  77.  
  78.                          // Quebra de linha
  79.                          insertLineBreakInByteArray(postData);
  80.  
  81.                          // Content Disposition
  82.                          insertStringInByteArray('Content-Disposition: form-data; name="' + parametro.nome + '"', postData);
  83.  
  84.                          // 2 Quebras de linha
  85.                          insertLineBreakInByteArray(postData);
  86.                          insertLineBreakInByteArray(postData);
  87.  
  88.                          // Conteúdo do parâmetro que será enviado
  89.                          insertStringInByteArray(parametro.valor, postData);
  90.  
  91.                          // Quebra de linha
  92.                          insertLineBreakInByteArray(postData);
  93.  
  94.                     }
  95.  
  96.                }
  97.  
  98.                // -- + boundary + --
  99.                insertStringInByteArray("--" + boundary + "--", postData);
  100.  
  101.                var urlRequest:URLRequest = new URLRequest;
  102.                urlRequest.url = url;
  103.                urlRequest.contentType = 'multipart/form-data; boundary=' + boundary;
  104.                urlRequest.method = URLRequestMethod.POST;
  105.                urlRequest.requestHeaders.push(new URLRequestHeader('Cache-Control', 'no-cache'));
  106.                urlRequest.data = postData;
  107.  
  108.                if (loader != null)
  109.                {
  110.                     loader.dataFormat = URLLoaderDataFormat.BINARY;
  111.                     loader.load(urlRequest);
  112.                }
  113.                else
  114.                {
  115.                     navigateToURL(urlRequest, "_blank");
  116.                }
  117.  
  118.           }
  119.  
  120.           private static function gerarBoundary():String
  121.           {
  122.                var boundary:String = "--";
  123.  
  124.                for (var i:int = 0; i <0x10; i++)
  125.                {
  126.                     boundary += String.fromCharCode(int( 97 + Math.random() * 25 ));
  127.                }
  128.  
  129.                return boundary;
  130.           }
  131.  
  132.           private static function insertStringInByteArray(value:String, byteArray:ByteArray):void
  133.           {
  134.                for (var i:int = 0; i <value.length; i++)
  135.                {
  136.                     byteArray.writeByte(value.charCodeAt(i));
  137.                }
  138.           }
  139.  
  140.           private static function insertLineBreakInByteArray(byteArray:ByteArray):void
  141.           {
  142.                byteArray.writeShort(0x0d0a);
  143.           }
  144.  
  145.      }
  146. }

Para enviar arquivos é muito simples, basta criar um ArrayCollection com os arquivos desejados e chamar o método "uploadFiles" da classe "UploadFiles" passando a URL e, se desejado, um "loader". O "loader" é utilizado para realizar a chamada sem abrir uma nova janela. Se o "loader" não for passado para o método, o Request será realizado em outra janela.

Para facilitar criamos a classe "File" para armazenar os atributos de cada arquivo:

Actionscript:
  1. package
  2. {
  3.      import flash.utils.ByteArray;
  4.  
  5.      public class File
  6.      {
  7.           public var fileName:String;
  8.           public var byteFile:ByteArray;
  9.           public var contentType:String = 'application/octet-stream';
  10.      }
  11. }

E a classe "Parametro" para armazenar os atributos de cada parâmetro:

Actionscript:
  1. package
  2. {
  3.      public class Parametro
  4.      {
  5.           public var nome:String;
  6.           public var valor:String;
  7.  
  8.           public function Parametro(nome:String, valor:String)
  9.           {
  10.                this.nome = nome;
  11.                this.valor = valor;
  12.           }
  13.      }
  14. }

Sabemos que este post é muito parecido com o "Salvando imagens da sua aplicação Flex no computador do Usuário", postado pelo Beck. Acreditamos até ser um complemento do post. Como já tinha muita gente pedindo para nós "postarmos" essa solução, nos empolgamos com o post do Beck e resolvemos escrever o nosso.

Bom, é isso! Abraço a todos!

Por Daniel Kanitz em 13/November/2007 | Comentar | Trackback


No Translations

Adicionar comentário

(requerido)
(requerido, não será publicado)