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":
-
package
-
{
-
import flash.net.URLLoader;
-
import flash.net.URLLoaderDataFormat;
-
import flash.net.URLRequest;
-
import flash.net.URLRequestHeader;
-
import flash.net.URLRequestMethod;
-
import flash.utils.ByteArray;
-
import flash.utils.Endian;
-
-
import mx.collections.ArrayCollection;
-
import flash.net.navigateToURL;
-
-
public class UploadFiles
-
{
-
-
public static function uploadFiles(url: String, files:ArrayCollection = null, parametros:ArrayCollection = null, loader:URLLoader = null):void
-
{
-
if (files == null && parametros == null) {
-
throw new Error("Nenhum arquivo ou parâmetro foi passado para o método 'uploadFiles'.");
-
}
-
-
var boundary: String = gerarBoundary();
-
var postData: ByteArray = new ByteArray;
-
-
postData.endian = Endian.BIG_ENDIAN;
-
-
//Gerando request para cada arquivo que será enviado
-
if (files != null)
-
{
-
-
for (var i:int = 0; i <files.length; i++)
-
{
-
-
var file:File = files.getItemAt(i) as File;
-
-
// -- + boundary
-
insertStringInByteArray("--" + boundary, postData);
-
-
// Quebra de linha
-
insertLineBreakInByteArray(postData);
-
-
// Content Disposition
-
insertStringInByteArray('Content-Disposition: form-data; name="' + file.fileName + '"; filename="' + file.fileName + '"', postData);
-
-
// Quebra de linha
-
insertLineBreakInByteArray(postData);
-
-
// Content Type
-
insertStringInByteArray("Content-Type: " + file.contentType, postData);
-
-
// 2 Quebras de linha
-
insertLineBreakInByteArray(postData);
-
insertLineBreakInByteArray(postData);
-
-
// Conteúdo do arquivo que será enviado
-
postData.writeBytes(file.byteFile, 0, file.byteFile.length);
-
-
// Quebra de linha
-
insertLineBreakInByteArray(postData);
-
-
}
-
-
}
-
-
//Gerando request para cada parametro que será enviado
-
if (parametros != null)
-
{
-
-
for (i = 0; i <parametros.length; i++)
-
{
-
-
var parametro:Parametro = parametros.getItemAt(i) as Parametro;
-
-
// -- + boundary
-
insertStringInByteArray("--" + boundary, postData);
-
-
// Quebra de linha
-
insertLineBreakInByteArray(postData);
-
-
// Content Disposition
-
insertStringInByteArray('Content-Disposition: form-data; name="' + parametro.nome + '"', postData);
-
-
// 2 Quebras de linha
-
insertLineBreakInByteArray(postData);
-
insertLineBreakInByteArray(postData);
-
-
// Conteúdo do parâmetro que será enviado
-
insertStringInByteArray(parametro.valor, postData);
-
-
// Quebra de linha
-
insertLineBreakInByteArray(postData);
-
-
}
-
-
}
-
-
// -- + boundary + --
-
insertStringInByteArray("--" + boundary + "--", postData);
-
-
var urlRequest:URLRequest = new URLRequest;
-
urlRequest.url = url;
-
urlRequest.contentType = 'multipart/form-data; boundary=' + boundary;
-
urlRequest.method = URLRequestMethod.POST;
-
urlRequest.requestHeaders.push(new URLRequestHeader('Cache-Control', 'no-cache'));
-
urlRequest.data = postData;
-
-
if (loader != null)
-
{
-
loader.dataFormat = URLLoaderDataFormat.BINARY;
-
loader.load(urlRequest);
-
}
-
else
-
{
-
navigateToURL(urlRequest, "_blank");
-
}
-
-
}
-
-
private static function gerarBoundary():String
-
{
-
var boundary:String = "--";
-
-
for (var i:int = 0; i <0x10; i++)
-
{
-
boundary += String.fromCharCode(int( 97 + Math.random() * 25 ));
-
}
-
-
return boundary;
-
}
-
-
private static function insertStringInByteArray(value:String, byteArray:ByteArray):void
-
{
-
for (var i:int = 0; i <value.length; i++)
-
{
-
byteArray.writeByte(value.charCodeAt(i));
-
}
-
}
-
-
private static function insertLineBreakInByteArray(byteArray:ByteArray):void
-
{
-
byteArray.writeShort(0x0d0a);
-
}
-
-
}
-
}
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:
-
package
-
{
-
import flash.utils.ByteArray;
-
-
public class File
-
{
-
public var fileName:String;
-
public var byteFile:ByteArray;
-
public var contentType:String = 'application/octet-stream';
-
}
-
}
E a classe "Parametro" para armazenar os atributos de cada parâmetro:
-
package
-
{
-
public class Parametro
-
{
-
public var nome:String;
-
public var valor:String;
-
-
public function Parametro(nome:String, valor:String)
-
{
-
this.nome = nome;
-
this.valor = valor;
-
}
-
}
-
}
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!

