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”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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:

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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!


2 comentários

  1. Thiago em 29.mai.09 às 11:48 am

    PARABÉNS!!

    Pesquisei em quase toda a web sem sucesso. Mas tem um ditado que diz: “Quem procura acha” e “Quem busca encontra”. Pois bem, valew mesmo excelente a ajuda.
    Porém, tá dando um erro na package UploadFiles.

    1037: Packages cannot be nested line 2

    o que seria?

  2. Flávio Otoni em 28.set.10 às 11:37 am

    Ola Daniel Kanitz
    Cara gostei muito dessa classe, porem não entendi muito bem como usar ela.
    A parte da classe File e Pessoa.
    E como recuperar o ByteArray do outro lado no caso do meu Java.

    Se não for pedir muito vc teria um exemplo?

    Abraço e sucesso.

Deixe Seu Comentário