Skip to content

Category Archives: webdev

Django, download e o problema no Internet Explorer 7

09-May-08

Há pouco tempo atrás eu postei aqui no blog como forçar o download de arquivos com Django.

Na época eu havia testado o sistema em todos os navegadores que estavam ao meu alcance (Firefox, Iceweasel, Opera, Epiphany, IE6 e IE7) porém mesmo assim não precisou de muito tempo em ambiente de produção para me contactarem dizendo que o sistema não estava funcionando no IE7 (ué, como assim!? mas eu tinha testado isso!).

Testei novamente e então descobri que na verdade ele funcionava e não funcionava. Hm, ok… mas como assim!? Na verdade ele funcionava perfeitamente no IE7 do Windows 2003 Server, mas não funcionava em nenhum outro e eu dei o azar (ou sorte) de testar justamente nele… no Windows 2003 Server. Nos Windows com problemas (pleonasmo?) o erro que ocorria era o seguinte:

Internet Explorer was not able to open this Internet site.
The requested site is either unavailable or cannot be found. Please try again later.

Ok, um erro muito explicativo… Então vamos ao Google! Depois de muito procurar no Google sem encontrar nenhuma solução, um colega de empresa decidiu comparar os headers de uma aplicação escrita em .NET e que funcionava em qualquer IE, com os headers gerados pela minha aplicação. Chegamos a conclusão de que o problema estava no campo Vary, que aparecia nos headers do Django mas não existia no header da aplicação .NET.

A primeira idéia foi remover o campo manualmente, antes de retornar o response da página:


from django.http import HttpResponse, Http404
from os import path
import mimetypes

def index(request, file=''):
    abspath = path.join('/your/absolute/path', file)
    if not (file and path.exists(abspath)):
        raise Http404()
    mimetype, encoding = mimetypes.guess_type(abspath)
    if mimetype is None:
        mimetype = 'application/force-download'
    response = HttpResponse(open(abspath, 'r').read())
    response['Content-Disposition'] = 'attachment; filename=%s' % file
    response['Content-Type'] = mimetype
    response['Content-Length'] = str(path.getsize(abspath))
    del response['Vary']
    return response

* As linhas em negritos são as linhas que foram alteradas na solução inicial.

Mas não funcionou… O campo Vary continuava aparecendo e com o mesmo valor de antes. Lendo um pouco de documentação, descobrimos que essa diretiva não pode ser alterada diretamente no header da página e também qual era a forma correta de remover esse campo:


from django.views.decorators.cache import cache_control
from django.http import HttpResponse, Http404
from os import path
import mimetypes

@cache_control(private=True)
def index(request, file=''):
    abspath = path.join('/your/absolute/path', file)
    if not (file and path.exists(abspath)):
        raise Http404()
    mimetype, encoding = mimetypes.guess_type(abspath)
    if mimetype is None:
        mimetype = 'application/force-download'
    response = HttpResponse(open(abspath, 'r').read())
    response['Content-Disposition'] = 'attachment; filename=%s' % file
    response['Content-Type'] = mimetype
    response['Content-Length'] = str(path.getsize(abspath))
    return response

* As linhas em negritos são as linhas que foram alteradas na solução inicial.

Ok, agora sim! Tudo funcionando direitinho e em todos os (pseudo-)navegadores!

Como eu não achei nada na internet que ligasse esse problema aos campos Vary do header, acredito que esse post possa ajudar alguém mais que esteja passando pelo menos problema! :)

UPDATE: Em alguns casos a conexão terminava antes de ter baixado todo o arquivo. Esse é um problema do servidor, e para corrigi-lo basta definir a diretiva EnableSendfile do Apache como On. Com essa configuração, também houve uma melhora significativa na taxa de download do servidor (pelo menos no meu caso).

Forçando o download de arquivos com Django

08-Apr-08

Primeiro post pelo Planeta GNU/Linux Brasil. :)

Essa semana precisei criar uma aplicação web que disponibilizasse alguns arquivos para download e então decidi fazer isso utilizando Python e Django.

Uma parte da minha aplicação consistia em forçar o browser a abrir uma janela de download ao invés de exibir o arquivo (como acontece no caso de imagens ou arquivos texto). Eu já havia feito isso algumas vezes em PHP, mas fui pesquisar para ver se o Django não possuia alguma função pronta que me facilitasse essa tarefa, algo como:


return HttpResponseFile(file)

Passei algum tempo procurando por alguma solução mais inteligente do que alterar o header na mão, mas não consegui encontrar nada. Fui então atrás da documentação oficial, para descobrir como eu podia aplicar a solução que eu já utilizava em PHP para a realidade do Django. Encontrei aqui a “receita de bolo” de como alterar o header da requisição e fazer com que o browser agisse da forma como eu queria.

Portanto segue abaixo, a parte principal da minha implementação:

urls.py

Esse arquivo tem uma única função: extrair da url o nome do arquivo a ser baixado e passa-lo como parametro para a view.


urlpatterns = patterns('',
    (r'(?P<file>(^.*$)', 'myproject.apps.download.views.index'),
)

views.py

Aqui é onde a mágica ocorre… na verdade não é nada muito mágico, mas enfim. :)

Essa view apenas verifica se o arquivo existe e o envia para download, caso o arquivo não exista, retorna a página 404. Nessa função você pode aplicar várias outras validações, como verificar se o usuário está logado ou se ele relamente tem acesso a esse arquivo, etc. porém nada disso era necessário no meu caso.


from django.http import HttpResponse, Http404
from django.conf import settings
from os import path
import mimetypes

def index(request, file=''):
    abspath = path.join(settings.BASEPATH, 'your/path', file)
    if not (file and path.exists(abspath)):
        raise Http404()
    mimetype, encoding = mimetypes.guess_type(abspath)
    if mimetype is None:
        mimetype = 'application/force-download'
    response = HttpResponse(open(abspath, 'r').read())
    response['Content-Type'] = mimetype
    response['Pragma'] = 'public'
    response['Expires'] = '0'
    response['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0'
    response['Content-Disposition'] = 'attachment; filename=%s' % file
    response['Content-Transfer-Encoding'] = 'binary'
    response['Content-Length'] = str(path.getsize(abspath))
    return response

Espero que seja de alguma utilidade… e se alguém tiver uma solução melhor! Por favor, poste nos comentários.

Não faz muito tempo que eu mexo com Django, mas estou gostando da brincadeira!
Acredito que o número de posts sobre Python desse blog tende a crescer consideravelmente… e isso é bom!

UPDATE1: Depois de algum tempo com o sistema em produção, foi constatado que a solução acima não funciona em algumas versão do Internet Explorer 7. A solução do problema também foi documentada e está disponível aqui.

UPDATE2: Adicionada a sugestão de Alexander Benatti com relação a criação da variável abspath.

Crypt class v2.1

11-Aug-07

Já há algum tempo que mantenho uma classe de criptografia no site PHP Classes.

Na época que publiquei o script, somente eu o utilizava e meu costume era de ter uma chave única de criptografia definida para todo o meu sistema, portanto essa chave era definida como uma constante da classe e não podia ser alterada dinamicamente. Porém nos últimos meses recebi email de vários usuários pedindo para que essa característica da classe fosse modificada, pois vários deles precisavam definir mais do que uma chave em seus sistemas. Então nesse final de semana, eu decidi trabalhar em cima da classe novamente para aplicar esta e mais algumas modificações que considero interessantes. Para aqueles que já utilizam a classe, por favor atualizem suas versões, e para aqueles que ainda não utilizam, está dada a dica. :)

Exemplo de Utilização


<?php
require_once 'Crypt.class.php';

$Crypt = new Crypt;
$Crypt->Mode = CRYPT_MODE_HEXADECIMAL;
$Crypt->Key = 'MY_CRYPT_KEY';
echo $Crypt->Encrypt('My data');
?>

Faça download da Crypt class v2.1

UPDATE:
Por sugestão do Leandro Camargo (nos comentários abaixo), adicionei este projeto ao Google Code e a partir de hoje estará disponível no seguinte endereço:

http://cryptclass.googlecode.com

jQuery e TableSorter sobre componentes .NET

03-Aug-07

Se você trabalha com .NET já deve ter notado que o código HTML gerado pelo framework não é o que podemos chamar de “um código bem estruturado”. Apesar disso, até agora isso nunca tinha me causado nenhum problema… Porém essa semana eu tive que fazer alguns malabarismos para poder utilizar o plugin TableSorter 2.0 da biblioteca jQuery em um projeto da empresa onde trabalho. O que eu estava tentando fazer era aplicar o plugin sobre uma tabela gerada pelo componente DataGrid do framework. O problema ocorreu porque plugin utiliza as linhas do <thead> da sua tabela para organizar a ordenação, mas o componente .NET não adiciona essa sessão ao código HTML da sua tabela.

Habilitando o TableSorter

Depois de algum tempo procurando por soluções prontas sem sucesso, eu implementei um código javascript para ser executado no evento onload da página que modifica a tabela adicionando o a sessão <thead> utilizada pelo TableSorter. Segue o código abaixo:


function _jQueryTableSorter_SemanticTable(tableId) {
    var table = document.getElementById(tableId)
    if (table == null)
        return false
    if (table.tHead != null && table.tHead.rows.length > 0)
        return true
    table.tHead || table.createTHead()
    var tHead = table.tHead
    var tFirstRow = table.rows[0]
    var tHeadRow = tHead.insertRow(0)
    tHeadRow.className = tFirstRow.className
    for (var i=0;i<tFirstRow.cells.length;i++) {
        var tHeadCell = tHeadRow.insertCell(tHeadRow.cells.length)
        tHeadCell.innerHTML = tFirstRow.cells[i].innerHTML
        tHeadCell.className = tFirstRow.cells[i].className
    }
    table.deleteRow(1);
    return true;
}

Isso acima resolveu o meu problema fazendo com que o plugin da jQuery conseguisse identificar o header da tabela e a ordenasse corretamente. Então caso encerrado, certo? Errado… O funcionamento correto do TableSorter acabou me gerando outro problema, pois a minha tabela possuia backgrounds alternados e no momento ordenação, essa alternância ficava completamente bagunçada.

Habilitando a alternância de estilos

Novamente procurando por solução prontas, eu encontrei na documentação do plugin uma solução pronta para o problema, porém que não não sei porque cargas d’águas não funcionou comigo. Para resolver agora o novo problema, eu desenvolvi um widget para trabalhar sobre o TableSorter. Segue o código abaixo:


function _jQueryTableSorter_AddWidgets() {
    $.tablesorter.addWidget({
        id: 'AlternatingRowStyle',
        format: function(table) {
            var tRows = table.tBodies[0].rows
            var classes = new Array;
            for (var i=0;i<tRows.length;i++) {
                var classExists = false
                for (var j=0;j<classes.length;j++)
                    if (classes[j] == tRows[i].className) {
                        classExists = true
                        break
                    }
                if (!classExists)
                    classes.push(tRows[i].className)
            }
            for (var i=0;i<tRows.length;i++)
                tRows[i].className = classes[i%classes.length];
        }
    });
}

Agora sim, problema resolvido! O TablerSorter funcionando perfeitamente nos DataGrid’s do .NET Framework.

Juntando os caquinhos

Para facilitar uso futuros eu ainda criei uma terceira que executa para executar as duas funções anteriores para mim, e o código final acabou ficando assim:


function jQueryTableSorter(tableId, options, defaultOrder) {
    _jQueryTableSorter_SemanticTable(tableId)
    _jQueryTableSorter_AddWidgets()
    $('#' + tableId).tablesorter(options || null)
    if (defaultOrder != undefined)
        $('#' + tableId).trigger('sorton', [ defaultOrder ]);
}

E agora, basta aplicar as funções acima sobre um DataGrid para habilitar ordenação client-side.

Botando para funcionar

Veja abaixo o exemplo da minha implementação, onde eu utilizo as funções acima juntamente com algumas outras configurações do TableSorter:


<script type="text/javascript">
    window.onload = function() { /* IE7 fix */
        var defaltOrder = [[ 2, 0 ]]
        jQueryTableSorter('dataGridId', {
            widgets:[ 'AlternatingRowStyle' ],
            sortInitialOrder:'asc',
            headers:{ 0:{ sorter:false } }
        }, defaultOrder)
    }
</script>

@todo unir essas funções para criar um plugin do plugin da jQuery :)

UPDATE: adicionado ao http://clientside.com.br/jquery-e-tablesorter-sobre-componentes-net/

Utilizando properties em PHP5

08-May-07

Se você trabalha com PHP Orientado à Objetos e gosta de manter seus dados encapsulados de uma forma “elegante”, você inevitavelmente acaba escrevendo métodos de set e get para os atributos privados da sua classe. Porém, a versão 5 do PHP conta com uma funcionalidade que pouquíssima gente utiliza (talvez porque a maioria dos programadores PHP não sabem programar) que são as Properties.

O que são Properties?

Uma property permite que você associe rotinas de acesso à um atributo da sua classe. Você consegue definir que sempre que houver uma atribuição sobre um determinado atributo do seu objeto, seja executado o método X ou então, sempre que esse valor for requisitado seja executado o método Y. O que acontece na realidade é que você define os métodos e regras de set e get para os seus atributos e faz com que eles sempre sejam executados de forma transparente pela sua classe, e assim você consegue utilizar normalmente seus atributos.

Veja um exemplo bem simples do conceito sendo aplicado:


<?php
class People {
    private $name;

    function __set($property, $value) {
        switch ($property) {
            case 'Name': $this->name = ucwords(strtolower($value)); break;
        }
    }

    function __get($property) {
        switch ($property) {
            case 'Name': return "Meu nome é {$this->name}."; break;
        }
    }
}

$p = new People();
$p->Name = 'ARTHUR FURLAN';
echo $p->Name;
?>

Resultado:


Meu nome é Arthur Furlan.

Repare que apesar de ter defindo o valor ‘ARTHUR FURLAN’ para o atributo, a string armazenada é ‘Arthur Furlan’. Como a atribuição ocorre dentro dos métodos __set() e __get(), você poderia ter uma infinidade de linhas, chamadas de funções e métodos tratando o valor passado antes de defini-lo no seu atributo.

Extendendo o conceito

Como você pôde ver, as rotinas de acesso do seus atributos são independentes. Você pode definir somente o método get e manter o set privado à classe, ou definir um método de set e manter o get privado (apesar de eu ainda não ter achado uma aplicação útil para esse caso =D). Isso é bastante útil, principalmente quando se está trabalhando com registros do banco de dados, pois o valor do campo ‘Id’ de um registro raramente é alterado. Então, nesse caso, seria interessante definirmos para ele, somente uma property de get e deixarmos que todas as suas atribuições sejam feitas internamente à classe.


<?php
class People {
    private $id;
    private $firstName;
    private $lastName;
    private $age;

    function __set($attribute, $value) {
        switch ($attribute) {
            case 'FirstName': $this->firstName = ucwords(strtolower($value)); break;
            case 'LastName' : $this->lastName  = ucwords(strtolower($value)); break;
            case 'Age'      : $this->age       = (int) $value; break;
        }
    }

    function __get($attribute) {
        switch ($attribute) {
            case 'Id'       : return $this->id;
            case 'FirstName': return $this->firstName;
            case 'LastName' : return $this->lastName;
            case 'Age'      : return $this->age;
        }
    }
}

$p = new People();
$p->FirstName = 'Arthur';
$p->LastName  = 'Furlan';
$p->Age       = 21;
echo "{$p->FirstName} {$p->LastName} ({$p->Age})";
?>

Observações

1. Repare que é possível atribuir valores à atributos que não existem fisicamente no seu objeto, desde que você crie uma property para tratar esse caso, assim como foi utilizado na classe acima.

2 Apesar de definir um atributo como privado e não criar método de get para ele, o programador poderá descobrir o valor do seu atributo através dos comandos print_r() ou var_dump(). Então, esse tipo de prática só não basta para manter um dado do objeto em sigilo.

Lista duplamente encadeada em PHP

22-Apr-07

Hoje eu estava conversando com o Diogo Menezes no messenger sobre como construir uma Lista Duplamente Encadeada em PHP, porque ele precisava criar uma para um trabalho dele da faculdade. Depois de algum tempo de conversa, eu acabei me empolgado com a discussão e resolvi implementar o algoritmo para ver como isso funcionava no PHP (que ao meu ver tem um suporte bem ruim à ponteiros).

Depois de algum tempo quebrando a cabeça para contornar algumas problemas do PHP (comentadas ao final do post) surgiu um primeiro protótipo funcional da lista, que segue abaixo:

Arquivo: Node.class.php

Estrutura utilizada para o armazenamento de valores. A lista na realidade é um grupo de objetos desta classe que apontam, de forma ordenada, para outros objetos desta mesma classe.


<?php
class Node {
    public $Next     = null;
    public $Previous = null;
    public $Data;

    function __construct($value) {
        $this->Data = $value;
    }
}
?>

Arquivo: DLinkedList.class.php

Classe que contém somente um atributo que é o nodo inicial da lista. Essa classe na realiade pode conter vários outros métodos que auxiliam na manutenção da sua lista.


<?php
require_once 'Node.class.php';

class DLinkedList {
    public $Head;

    function __construct($value) {
        $this->Head = new Node($value);
    }
}
?>

Arquivo: index.php

Exemplo de implementação da classe, apenas algoritmo bem simples para mostar o funcionamento.


<?php
require_once 'DLinkedList.class.php';

$List     = new DLinkedList(1);
$LastNode = &$List->Head;

for ($i=0;$i<10;$i++) {
    unset($Node);
    $Node           = new Node($i+2);
    $LastNode->Next = &$Node;
    $Node->Previous = &$LastNode;
    $LastNode       = &$Node;
}

$Node = &$List->Head;
do {
    echo "{$Node->Data}< br />";
    $Node = &$Node->Next;
} while(!is_null($Node->Next));
?>

Observações:

1. Se você é uma pessoa detalhista, deve ter notado no arquivo index.php, que o primeiro comando executado dentro do laço de criação dos nós da lista é o unset($Node); que segundo o manual oficial do PHP, Destrói a variável especificada. Ou seja, eu crio um novo nó, insiro ele na lista para logo em seguida destrui-lo!? Estranho não!? Pois é… Concordo em gênero, número e grau! Porém não é bem isso que acontece! Esse comando é realmente necessário, porque mesmo você alocando explicitamente um novo espaço de memória com new Node($i+2); o PHP não altera a referência na variável $Node e você acaba caindo um loop infinito, pois na segunda volta do laço $LastNodetem o mesmo valor de $Node e nós teremos um nó que aponta para ele mesmo.

2. Uma característica interessante do PHP neste caso, é o fato dele não ser tipado. Dessa forma é possível que essa sua lista guarda qualquer tipo de dados! Ela pode conter um inteiro no primeiro nodo, uma string no segundo, um objeto no terceiro e assim por diante…

3. Sim, eu sei que este código! Mas eu sempre tive vontade de ver como era uma implementação de lista em PHP.