Como criar Middlewares customizados no Django
Tags: python, django, webdev, tutorial
Published: 2023-03-08 21:58:22

Fala **Dev**! Na paz?!

Nesse meu primeiro post aqui na Dev.to você vai aprender a criar _Middlewares_ no Django, que são trechos de código para tratamento prévio ou posterior de requisições que chegam e saem do seu sistema Django.

_Ficou confuso? Então bora nessa!_ :wink:

## O que são _Middlewares_

_Middlewares_ vivem na [Camada View](https://pythonacademy.com.br/blog/desenvolvimento-web-com-python-e-django-view) do framework Web Django (que também é composto pela [Camada Model](https://pythonacademy.com.br/blog/desenvolvimento-web-com-python-e-django-model) e pela [Camada Template](https://pythonacademy.com.br/blog/desenvolvimento-web-com-python-e-django-template))

_Middlewares_ são trechos de códigos que podem ser executados antes ou depois do processamento de requisições/respostas pelo Django.

É uma forma que os desenvolvedores, nós, temos para alterar como o Django processa algum dado de entrada ou de saída.

Se você olhar no arquivo `settings.py`, nós temos a [lista](https://pythonacademy.com.br/blog/listas-no-python) `MIDDLEWARE` com diversos _middlewares_ pré-configurados:

```python
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
```

Por exemplo, o _middleware_ `AuthenticationMiddleware` é reponsável por adicionar a variável `user` a todas as requisições.

Dessa forma, você pode, por exemplo, mostrar o usuário logado no seu _template_:

```html
<li>
    <a href="/alguma/url">Olá, {{ user.email }}</a>
</li>
```

Vamos ver agora o ciclo de vida de um _Middleware_ dentro do Django.

## Ciclo de vida de um _Middleware_ no Django

Um _middleware_ é um método _callable_ (que implementa o método `__call__()`).

Esse método recebe uma **requisição** e retorna uma **resposta**, assim como uma _View_, podendo ser escrito como função ou como Classe.

Um exemplo de _middleware_ escrito como função é:

```python
def middleware_simples(get_response):
    # Código de inicialização do Middleware
    def middleware(request):
        # Aqui vai o código a ser executado antes 
        # da View e de outros middlewares
        # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        
        response = get_response(request)

        # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        # Aqui vai o código a ser executado 
        # para cada resposta após a View 
        
        return response

    return middleware
```

E como Classe:

```python
class MiddlewareSimples:
    def __init__(self, get_response):
        self.get_response = get_response
        # Código de inicialização do Middleware

    def __call__(self, request):
        # Aqui vai o código a ser executado antes 
        # da View e de outros middlewares
        # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        
        response = self.get_response(request)

        # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        # Aqui vai o código a ser executado 
        # para cada resposta após a View         

        return response
```

Como cada _Middleware_ é executado de maneira encadeada, do topo da configuração `MIDDLEWARE` (_do settings.py_) para o fim, a saída de um é a entrada do próximo.

O método `get_response()` pode ser a própria _View_, caso ela seja a última configurada no `MIDDLEWARE` do _settings.py_, ou o próximo _middleware_ da cadeia.

Utilizando a construção do _middleware_ via Classe, nós temos três métodos importantes: 

### O método `process_view`

:point_right: **Assinatura**: `process_view(request, func, args, kwargs)`

Esse método é chamado logo antes do Django executar a _View_ que vai processar a requisição e possui os seguintes parâmetros:

- `request` é o objeto `HttpRequest`.
- `func` é a própria _view_ que o Django está para chamar ao final da cadeia de _middlewares_.
- [`args`](https://pythonacademy.com.br/blog/args-e-kwargs-do-python) é a lista de parâmetros posicionais que serão passados à _view_.
- [`kwargs`](https://pythonacademy.com.br/blog/args-e-kwargs-do-python) é o _dict_ contendo os argumentos nomeados (_keyword arguments_) que serão passados à _view_.

Esse método deve retornar `None` ou um objeto `HttpResponse`:

- Caso retorne `None`, o Django entenderá que deve continuar a cadeia de _Middlewares_.
- Caso retorne `HttpResponse`, o Django entenderá que a resposta está pronta para ser enviada de volta e não vai se preocupar em chamar o resto da cadeia de _Middlewares_, nem a _view_ que iria processar a requisição.

### O método `process_exception`

:point_right: **Assinatura**: `process_exception(request, exception)`

Esse método é chamada quando uma _view_ lança uma exceção e deve retornar ou `None` ou `HttpResponse`. Caso retorne um objeto `HttpResponse`, o Django irá aplicar o _middleware_ de resposta e o de _template_, retornando a requisição ao _browser_.

- `request` é o objeto `HttpRequest` 
- `exception` é a exceção propriamente dita lançada pela _view_ (`Exception`).

### O método `process_template_response`

:point_right: **Assinatura**: `process_template_response(request, response)`

Esse método é chamado logo após a _view_ ter terminado sua execução, caso a resposta tenha uma chamada ao método `render()` indicando que a reposta possui um _template_.

Possui os seguintes parâmetros:

- `request` é um objeto `HttpRequest`.
- `response` é o objeto `TemplateResponse` retornado pela _view_ ou por outro _middleware_.

Agora vamos criar um _middleware_ um pouco mais complexo para exemplificar o que foi dito aqui!

## Criando um _Middleware_ no Django

Vamos supor que queremos um _middleware_ que filtre requisições e só processe aquelas que venham de uma determinada lista de IP's.

O que precisamos fazer é abrir o cabeçalho de todas as requisições que chegam no nosso servidor e verificar se o IP de origem bate com a nossa lista de IP's.

Para isso, colocamos a lógica no método `process_view`, da seguinte forma:

```python
class FiltraIPMiddleware:    

  def __init__(self, get_response=None):
    self.get_response = get_response

  def __call__(self, request):
    response = self.get_response(request)

    return response

  def process_view(request, func, args, kwargs):
    # Lista de IPs autorizados
    ips_autorizados = ['127.0.0.1']

    # IP do usuário
    ip = request.META.get('REMOTE_ADDR')

    # Verifica se o IP do cliente está na lista de IPs autorizados
    if ip not in ips_autorizados:
      # Se usuário não autorizado > HTTP 403: Não Autorizado
      return HttpResponseForbidden("IP não autorizado")

    # Se for autorizado, não fazemos nada
    return None
```

Depois disso, precisamos registrar nosso _middleware_ no arquivo de configurações _settings.py_ (na configuração `MIDDLEWARE`):

```python
MIDDLEWARE = [
  # Middlewares do próprio Django
  'django.middleware.security.SecurityMiddleware',
  'django.contrib.sessions.middleware.SessionMiddleware',
  'django.middleware.common.CommonMiddleware',
  'django.middleware.csrf.CsrfViewMiddleware',
  'django.contrib.auth.middleware.AuthenticationMiddleware',
  'django.contrib.messages.middleware.MessageMiddleware',
  'django.middleware.clickjacking.XFrameOptionsMiddleware',

  # Nosso Middleware
  'helloworld.middlewares.FiltraIPMiddleware',
]
```

Agora, podemos testar seu funcionamento alterando a lista `ips_autorizados`:

- Coloque `ips_autorizados = ['127.0.0.1']` e tente acessar alguma URL da nossa aplicação: devemos conseguir acessar normalmente nossa aplicação, pois como estamos executando o servidor localmente, nosso IP será 127.0.0.1 e, portanto, passaremos no teste.
- Coloque `ips_autorizados = []` e tente acessar alguma URL da nossa aplicação: deve aparecer a mensagem de **"IP não autorizado"**, pois nosso IP (127.0.0.1) não está autorizado a  acessar o servidor.

## Conclusão

Nesse primeiro _post_ quis trazer como é simples criar _Middlewares_ no Django.

Você viu como podemos adicionar tratamento prévio ao processamento de requisições e posterior ao processamento de respostas.

Seus casos de uso são bem específicos, contudo é bom saber que temos essa possibilidade!

É isso dev, nos vemos na próxima! :wink: