Intermediário, Vais Embora?

No artigo anterior da série Entender a Django, cobrimos o sistema de autenticação embutido. Este artigo nos deu a oportunidade de ver o modelo de base de dados User, maneiras de iniciar a sessão dos utilizadores com as ferramentas de autenticação da Django, e as funcionalidades que fazem os controlos de autorização funcionarem. Neste tópico, o intermediário surgiu como um componente integral. Agora aprenderemos mais sobre o intermediário e sua função dentro de um projeto de Django.

  1. Do Navegador à Django
  2. URLs Guiam o Caminho
  3. Visões Sobre Visões
  4. Modelos de Marcação para as Interfaces de Utilizador
  5. Interação do Utilizador com os Formulários
  6. Armazenar Dados com Modelos de Base de Dados
  7. Administrador de Todas as Coisas
  8. A Anatomia Duma Aplicação
  9. Autenticação do Utilizador
  10. Intermediário, Vais Embora?

Como Devo Pensar em Intermediário?

Para começar este tópico, descubramos onde o intermediário existe num projeto de Django.

O intermediário é o código que existe no meio. “No meio de quê?”, podemos perguntar. O “meio” é o código executado entre quando uma HttpRequest é criada pela abstração e quando o código que escrevemos é chamado pela Django. O “meio” também pode referir-se ao código executado depois que a nossa visão completa, mas antes da Django traduzir a HttpResponse em bytes para enviá-la através da rede a um navegador.

Já alguma vez comemos um Rebuçado Eterno? Não, não falo daquele do Willy Wonka que dura para sempre. Um Rebuçado Eterno é um doce duro, em camadas, que muda de cor e sabor à medida que o mantemos na boca até chegarmos finalmente a um centro macio.

O intermediário é uma espécie de camada de doces e o código da nossa visão é como o centro macio. Minha analogia é quebrada quando pensamos em como alguém come o rebuçado.

Com o doce, experimentamos uma camada de cada vez até chegarmos ao meio, e já está. Uma comparação mais adequada com o intermediário seria escavar através das camadas e sair do outro lado, experimentando as mesmas chamadas na ordem oposta à maneira como entrámos.

O que é mostrado abaixo é um diagrama de todo o intermediário padrão incluído quando executamos o comando startproject. Se formos aprendizes visuais que não acharam útil a analogia do rebuçado, então espero que esta imagem seja mais ilustrativa:

               +--------- SecurityMiddleware --------------+
               |+-------- SessionMiddleware --------------+|
               ||+------- CommonMiddleware --------------+||
               |||+------ CsrfViewMiddleware -----------+|||
               ||||+----- AuthenticationMiddleware ----+||||
               |||||+---- MessageMiddleware ----------+|||||
               ||||||+--- XFrameOptionsMiddleware ---+||||||
               |||||||                               |||||||
HttpRequest =================> view function ==================> HttpResponse
               |||||||                               |||||||

Como a Django faz esta estratificação funcionar? Quando iniciamos a Django com um servidor de aplicações como o Gunicorn, nós temos que dar ao servidor de aplicações o caminho para o nosso módulo da Interface de Porta de Entrada do Servidor da Web (WSGI). Cobriremos servidores de aplicação num tópico posterior, mas, por enquanto, saibamos que um servidor de aplicação pode executar a nossa aplicação de Django. Se o nosso diretório de projeto que contém o nosso ficheiro de definições se chama project, então chamar o Gunicorn é como:

$ gunicorn project.wsgi

Teríamos esta configuração se executássemos django-admin startproject project . (incluindo o último ponto), mas o que é realmente necessário para o servidor de aplicação é onde quer que o nosso ficheiro wsgi.py estiver localizado no nosso projeto, na forma de caminho de módulo.

Precisamos lembrar, lá atrás no primeiro artigo da série, que WSGI significa Web Server Gateway Interface (Interface de Porta de Entrada do Servidor da Web) e é uma camada comum que aplicações síncronas da Web de Python devem implementar para trabalhar com os servidores de aplicação da Python. Dentro deste módulo project.wsgi existe uma função chamada get_wsgi_application, importada de django.core.wsgi.

A get_wsgi_application faz duas coisas:

  • Chama django.setup que faz toda a configuração de inicialização que vimos no último artigo
  • Retorna uma instância de WSGIHandler

Como podemos imaginar, a WSGIHandler está desenhada para fazer a interface de porta de entrada do servidor da Web funcionar, mas também é uma subclasse de django.core.handlers.base.BaseHandler. Esta classe manipuladora de base é onde a Django lida com a configuração do intermediário.

A manipuladora de base inclui um método load_middleware. Este método tem a função de iterar por todos os intermediários listados na nossa configuração MIDDLEWARE. Ao iterar através da MIDDLEWARE, o objetivo principal do método é incluir cada intermediário na cadeia de intermediário.

A cadeia de intermediário é o rebuçado da Django.

A cadeia representa cada instância do intermediário da Django, em camadas, para produzir o efeito desejado de permitir que uma requisição e uma resposta passem por cada intermediário.

Além de construir a cadeia de intermediário, a load_middleware deve fazer algumas outras configurações importantes.

  • O método lida com intermediário síncrono e assíncrono. Conforme a Django aumenta o seu suporte ao desenvolvimento assíncrono, os internos da Django precisam gerir as diferenças. A load_middleware faz algumas alterações dependendo do que esta pode descobrir sobre uma classe intermediária.
  • O método regista um intermediário com determinados conjuntos de intermediário com base na presença de vários métodos gatilhos. Falaremos sobre estas funções gatilhos mais adiante neste artigo.

Isto explica a estrutura do intermediário e como todo intermediário interage com o ciclo de vida da requisição e da resposta, mas o que o intermediário faz?

Podemos utilizar o intermediário para uma grande variedade de objetivos. Devido à cadeia de intermediário, uma requisição de protocolo de hipertexto bem-sucedida passará por todos os intermediários. Esta propriedade do intermediário torna-o ideal para código que queremos executar globalmente para o nosso projeto de Django.

Por exemplo, pensemos no nosso último artigo sobre Autenticação do Utilizador. Naquele artigo, observamos que o sistema de autenticação da Django é dependente da AuthenticationMiddleware. Este intermediário tem o trabalho individual de adicionar uma propriedade user a cada objeto de HttpRequest que passa pela aplicação antes que a requisição chegue ao código de visualização.

A AuthenticationMiddleware destaca algumas qualidades boas do intermediário na Django.

  • Idealmente, um intermediário deve ter um objeto objetivo restrito e individual.
  • Um intermediário deve executar uma quantidade mínima de código.

Por quê? Mais uma vez, a resposta está relacionada com a cadeia de intermediário. Uma vez que a requisição de protocolo de hipertexto passará por todos os intermediários da cadeia, podemos ver que cada intermediário será executado para cada requisição. Por outras palavras, cada intermediário tem um custo adicional de desempenho para cada requisição.

uma exceção a este comportamento da cadeia. Um intermediário no início da cadeia pode impedir a execução de intermediário mais tarde na cadeia.

Por exemplo, o SecurityMiddleware é o primeiro na cadeia de intermediário predefinida de um projeto gerado pelo startproject. Este intermediário está desenhado para efetuar algumas verificações para manter a aplicação segura. Uma verificação consiste em procurar uma conexão segura (ou seja, uma requisição que utilize o protocolo de hipertexto seguro) se o protocolo de hipertexto seguro estiver configurado. Se uma requisição chegar à aplicação e utilizar o protocolo de hipertexto em vez do protocolo de hipertexto seguro, o intermediário pode retornar um HttpResponsePermanentRedirect que redireciona para o mesmo endereço de localização de recurso com https:// e impede a execução do resto da cadeia.

Para além deste comportamento excecional no intermediário, é importante lembrarmos que, na maioria das circunstâncias, cada intermediário executar-se-á para cada requisição. Devemos ter em conta este aspeto do desempenho quando criarmos o nosso próprio intermediário.

Agora estamos prontos para aprender sobre como podemos criar o nosso próprio intermediário!

Como Posso Escrever O Meu Próprio Intermediário Personalizado?

Assumiremos que encontrámos um bom caso para criar um intermediário. Precisamos de algo que aconteça com todos os pedidos e que esta funcionalidade tenha um objetivo restrito.

Podemos começar com uma definição de intermediário vazia. No meu exemplo, coloraremos o intermediário num ficheiro middleware.py:

# project/middleware.py
class AwesomeMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        return self.get_response(
            request
        )

Depois de criar o intermediário, adicionamo-lo às nossas definições:

# project/settings.py

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    ...,
    'middleware.AwesomeMiddleware',
]

E pronto! Este intermediário personalizado não faz nada, exceto diminuir ligeiramente o desempenho porque é uma chamada de método adicional em cada requisição. Visto que coloquei o intermediário no final da lista MIDDLEWARE, este será o último intermediário a executar antes duma visão receber uma requisição e o primeiro intermediário com a oportunidade de processar uma resposta.

Podemos explicar como esta classe funciona:

  • O método __init__ obtém um objeto executável que é de maneira convencional chamado de get_response. O intermediário é criado durante load_middleware e o objeto executável é uma parte chave do que faz cadeia de intermediário funcionar. O objeto executável chamará o intermediário seguinte ou a visão, dependendo da posição do intermediário atual na cadeia.
  • O método __call__ transforma a própria instância do intermediário num objeto executável. O método deve chamar get_response para garantir que a cadeia não seja interrompida.

Se quisermos fazer um trabalho adicional, podemos fazer alterações ao método __call__. Podemos modificar __call__ para processar alterações antes ou depois da chamada de get_response. No ciclo de vida do pedido ou resposta, as alterações anteriores a get_response ocorrem antes da visão ser chamada, enquanto as alterações posteriores tratar da própria response ou de qualquer outro processamento posterior ao pedido.

Digamos que queremos que o nosso intermediário de exemplo registe algumas informações de tempo. Podemos atualizar o código para que fique com o seguinte aspeto:

# project/middleware.py
import logging
import time

logger = logging.getLogger(__name__)


class AwesomeMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        before_timestamp = time.time()
        logger.info(
            f"Tracking {before_timestamp}"
        )

        response = self.get_response(request)

        after_timestamp = time.time()
        delta = after_timestamp - before_timestamp
        logger.info(
            f"Tracking {after_timestamp} for a delta of {delta}"
        )

        return response

Nós ainda não falámos sobre o registo mas podemos entendê-lo como a gravação de mensagens para uma fonte de saída, como um ficheiro.

Este exemplo atua como um monitor de desempenho rudimentar. Se quiséssemos medir o tempo de resposta duma visão, este intermediário fá-lo-ia. A desvantagem é que não nos diz qual visão é registada. Ei, deixa-me em paz, este é um exemplo ridículo! 🤪

Esperemos que estejamos a começar a ver como o intermediário pode ser útil. Mas espera! Há mais coisas que o intermediário pode fazer.

Um intermediário de Django pode definir qualquer um dos três diferentes métodos gatilhos que a Django executará em diferentes partes do ciclo de vida do pedido ou resposta. Os três métodos são:

  • process_exception — Esta função gatilho é chamada sempre que uma visão levantar uma exceção. Isto poderia incluir uma exceção não capturada da visão, porém a função gatilho também receberá exceções, intencionalmente levantadas, como Http404.
  • process_template_response — Esta função gatilho é chamada sempre que uma visão retornar uma resposta que se parece com uma resposta do modelo de marcação (ou seja, o objeto da resposta tem um método render).
  • process_view — Esta função gatilho é chamada imediatamente antes da visão.

Voltando ao nosso exemplo ridículo, podemos torná-lo menos ridículo usando a função gatilho process_view. Veremos o que podemos fazer:

# project/middleware.py
import logging
import time

logger = logging.getLogger(__name__)


class AwesomeMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        before_timestamp = time.time()
        logger.info(
            f"Tracking {before_timestamp}"
        )

        response = self.get_response(request)

        after_timestamp = time.time()
        delta = after_timestamp - before_timestamp
        logger.info(
            f"Tracking {after_timestamp} for a delta of {delta}"
        )

        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        logger.info(
            f"Running {view_func.__name__} view"
        )

Agora, o nosso intermediário usa as capacidades de reflexão da Python para registar o nome da visão. Se acessarmos a administração da Django com um utilizador não autenticado, o histórico de registo pode registar algo como:

Tracking 1607438038.232886
Running login view
Tracking 1607438038.261855 for a delta of 0.02896881103515625

Este intermediário ainda poderia beneficiar-se de muito polimento, mas podemos como as funções gatilhos possibilitam que um intermediário tenha uma funcionalidade mais avançada.

Como um exemplo do intermediário process_exception, consideremos um serviço que coleta e reporta exceções para rastrear a saúde da nossa aplicação. Existem muitos destes serviços, como o Rollbar e o Sentry. Sou um utilizador da Rollbar, por isto comentarei esta questão. Podemos ver no código da pyrollbar que o serviço envia informações de exceção da função gatilho process_exception para o Rollbar através da função rollbar.report_exc_info. Sem intermediário, capturar e reportar exceções seria significativamente mais difícil.

Quer saber mais sobre as funções gatilhos? Podemos ver todos os detalhes sobre estas funções gatilhos na documentação do intermediário.

Que Intermediário a Django Inclui?

Analisámos o modelo mental do intermediário e todos os detalhes do funcionamento de um intermediário específico. Qual intermediário a Django inclui na abstração?

A lista completa de intermediário embutido está disponível na referência do intermediário. Descreverei o que acho que são as classes de intermediário mais comuns e úteis que a Django inclui.

  • AuthenticationMiddleware — Já encontrámos este intermediário na exploração do sistema de autenticação. O trabalho deste intermediário é adicionar o atributo user a um objeto HttpRequest. Este pequeno atributo user é responsável por muitas das funcionalidades do sistema de autenticação.
  • CommonMiddleware — O intermediário comum é um pouco estranho. Este intermediário lida com uma variedade de definições da Django para controlar certos aspetos do nosso projeto. Por exemplo, a definição APPEND_SLASH redirecionará um pedido como example.com/accounts para example.com/accounts/. Esta configuração só funciona se o CommonMiddleware estiver incluído.
  • CsrfViewMiddleware — No artigo de formulários, mencionei o símbolo de falsificação de pedidos entre sítios (ou CSRF). Relembraremos que se trata duma funcionalidade de segurança que ajuda a proteger o nosso projeto contra origens maliciosas que pretendem enviar dados incorretos ao nosso sítio. O CsrfViewMiddleware garante que o símbolo de falsificação de pedidos entre sítios está presente e é válido nas submissões de formulários.
  • LocaleMiddleware — Este intermediário serve para tratar das traduções se optarmos por internacionalizar o nosso projeto.
  • MessageMiddleware — O intermediário de mensagens é para “mensagens instantâneas”. Estas são mensagens pontuais que provavelmente veríamos após submeter um formulário, embora possam ser utilizadas em muitos lugares. Falaremos mais sobre as mensagens quando chegarmos ao tema das sessões.
  • SecurityMiddleware — O intermediário de segurança inclui uma série de verificações para ajudar a manter o nosso sítio seguro. Vimos o exemplo de verificação do protocolo seguro de hipertexto (HTTPS) anteriormente neste artigo. Este intermediário também lida com coisas como infiltração entre sítios (XSS), segurança de transporte restrito do protocolo de hipertexto (HSTS), e uma série de outros acrónimos (😛) que serão vistos no futuro tópico de segurança.
  • SessionMiddleware — O intermediário de sessão gere o estado da sessão para um utilizador. As sessões são cruciais para muitas partes da Django, como a autenticação de utilizadores.

Como podemos ver nesta lista incompleta, o intermediário da Django pode fazer muito para enriquecer o nosso projeto numa grande variedade de maneiras. O intermediário é um conceito extremamente poderoso para projetos de Django e uma ótima ferramenta para estender o tratamento de pedidos da nossa aplicação.

Lembremos que o intermediário tem um custo de desempenho, pelo que devemos evitar a tentação de colocar demasiadas funcionalidades na cadeia de intermediário. Desde que estejamos conscientes das vantagens e desvantagens, o intermediário é uma ótima ferramenta para o nosso cinto de ferramentas.

Sumário

Neste artigo, vimos o sistema de intermediário da Django.

Discutimos:

  • O modelo mental para considerar o intermediário
  • Como escrever o nosso próprio intermediário
  • Algumas classes de intermediário que vêm com a Django

Da próxima vez, analisaremos os ficheiros estáticos. Os ficheiros estáticos são todas as imagens, .js, .css, ou outros tipos de ficheiros servidos pela nossa aplicação, sem modificações, a um utilizador.

Precisamos de entender:

  • Como configurar os ficheiros estáticos
  • A maneira de trabalhar com os ficheiros estáticos
  • Como lidar com os ficheiros estáticos ao implantar o nosso sítio na Internet

Se gostarias de seguir juntamente com a série, sinta-se a vontade para inscrever-se no meu boletim informativo onde anuncio todos os meus novos conteúdos. Se tiveres outras questões, podes contactar-me na X onde sou o @mblayman.  

A tradução deste artigo para o português é cortesia de Nazaré Da Piedade.

Translations