Executando verificação de segurança...
16

Desvende o poder do Python puro: Crie seu site do zero sem frameworks (Com CGI)

O que é CGI?

CGI (Common Gateway Interface) é uma especificação padrão que permite a um servidor web transferir dados para e de aplicações externas. Na prática, CGI permite a execução de scripts que geram conteúdo dinâmico em um servidor web.

Funcionamento

Quando uma requisição atende uma diretiva configurada no servidor, ele cria um processo adicional e passa as informações da requisição via stdin para o script configurado. Este script, que pode ser escrito em diversas linguagens como Python, processa os dados e gera o conteúdo dinâmico que é devolvido ao servidor e, por fim, ao cliente (navegador).

Cenário Prático

Imagine que você precisa criar um formulário para coletar dados rapidamente e só tem um servidor virtual com Python disponível. Usando CGI, você pode configurar um servidor web rapidamente sem precisar de frameworks como Flask ou Django.

"Mas Ricardo, por que não usar algo como FastAPI?" Porque você tem pouco tempo para cumprir essa tarefa e não pode criar mais um serviço para o SRE da sua empresa monitorar.

Vou pular a parte de configurar a máquina e supor que você já leu o artigo como configurar uma nova VPS Linux com segurança usando o Ansible e já tem acesso seguro à máquina fornecida.

Passos para Implementação

  1. Instale o Apache
sudo apt-get install apache2

Use o comando python3 --version para verificar se a máquina já tem o Python instalado, na maioria das distribuições Linux ele já vem instalado por padrão.

  1. Ative o módulo CGI no Apache:
sudo a2enmod cgi
  1. Configure o Apache:

Adicione a seguinte diretiva no arquivo de configuração do Apache para permitir a execução de scripts CGI:

<Directory "/var/www/html/">
    AllowOverride None
    Options +ExecCGI
    AddHandler cgi-script .py
    Require all granted
</Directory>

Neste exemplo, qualquer arquivo dentro do diretório /var/www/html/ com a extensão .py será tratado como um script CGI e executado como tal.

  1. Crie e configure um script Python:
cd /var/www/html
touch hello.py
chmod +x hello.py

Conteúdo do hello.py:

#!/usr/bin/python3

import os

print("Content-type: text/html\n")
print("<html><head><title>CGI Info</title></head><body>")
print("<h1>CGI Info</h1>")

for key, value in os.environ.items():
    print(f"<p><strong>{key}:</strong> {value}</p>")

print("</body></html>")

A simplicidade desse script é notável: o servidor web utiliza o shebang para identificar o interpretador a ser usado e passa várias variáveis de ambiente definidas pelo Apache no processo CGI atual. Essas variáveis podem variar com novas solicitações no mesmo endereço, refletindo diferentes dados da requisição. Isso permite que o script CGI responda dinamicamente com base nas informações da requisição.

Agora basta acessar a porta 80 do servidor, por exemplo, http://146.190.78.111/hello.py.

Eu poderia encerrar aqui, pois você já aprendeu como o CGI funciona e como servir um site em Python sem um framework web. No entanto, vamos dar um passo adiante: criar um formulário, coletar os dados e ver tudo funcionando na prática.

Criação de um Formulário

Crie um formulário HTML que coleta dados e os envia para um script CGI Python que processa esses dados e os salva em um arquivo CSV.

  1. Formulário HTML formulario.py:
#!/usr/bin/python3

print("Content-type: text/html\n")
print("""
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Formulário CGI</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1 class="mt-5">Formulário de Sugestões</h1>
        <form method="post" action="/process_form.py">
            <div class="form-group">
                <label for="nome">Nome</label>
                <input type="text" class="form-control" id="nome" name="name" required>
            </div>
            <div class="form-group">
                <label for="sobrenome">Sobrenome</label>
                <input type="text" class="form-control" id="sobrenome" name="surname" required>
            </div>
            <div class="form-group">
                <label for="cargo">Cargo</label>
                <select class="form-control" id="cargo" name="office" required>
                    <option value="Desenvolvedor">Desenvolvedor</option>
                    <option value="Designer">Designer</option>
                    <option value="Gerente">Gerente</option>
                </select>
            </div>
            <div class="form-group">
                <label for="sugestao">Sugestão</label>
                <textarea class="form-control" id="sugestao" name="suggestion" rows="4" required></textarea>
            </div>
            <button type="submit" class="btn btn-primary">Enviar</button>
        </form>
    </div>
</body>
</html>
""")

Veja só, que interessante, o que esse script faz é apenas printar um código HTML e o navegador renderiza isso para o usuário como uma página.

Mas por que isso acontece? O CGI envia os dados do stdout para o servidor web, que os renderiza para o cliente. Quando usamos print, estamos enviando dados para o stdout. A primeira linha após o shebang define o tipo de conteúdo que o servidor deve renderizar, como HTML, mas poderia ser JSON, por exemplo.

E vamos para a parte mais interessante, quando esse formulário é preenchido e enviado, ele faz uma requisição POST para outro script, proccess_form.py que também será servido como CGI. Vamos criar o script que processará essa requisição, salvando os dados em um arquivo CSV no servidor. Esse script aceitará apenas requisições do tipo POST.

Para começar, importamos as bibliotecas necessárias. Em seguida, configuramos o tipo de conteúdo que será enviado para a saída padrão stdout e implementamos uma condição para permitir somente requisições do tipo POST. Caso uma requisição do tipo GET seja feita no script, uma mensagem de erro será retornada.

#!/usr/bin/python3

import csv
import os
import sys

print("Content-type: text/html\n")

if os.environ['REQUEST_METHOD'] == 'POST':
    
else:
    print("<html><body><h1>Método não permitido. Use POST.</h1></body></html>")

Agora precisamos recuperar algumas variáveis de ambiente criadas pelo servidor, assim como fizemos com REQUEST_METHOD. Vamos usar esse método para acessar o conteúdo do corpo da requisição. Poderíamos usar uma biblioteca Python chamada cgi, mas neste caso, vamos fazer manualmente para facilitar o entendimento de como isso funciona.

#!/usr/bin/python3

import csv
import os
import sys

print("Content-type: text/html\n")

if os.environ['REQUEST_METHOD'] == 'POST':
    content_length = int(os.environ['CONTENT_LENGTH']
    post_data = sys.stdin.read(content_length)

else:
    print("<html><body><h1>Método não permitido. Use POST.</h1></body></html>")

O que estamos fazendo nesse trecho é pegar o comprimento dos dados do corpo da requisição. Como já sabemos, uma variável de ambiente é uma string, então a convertemos para inteiro. Com o comprimento em mãos, pegamos exatamente os dados que queremos no stdin. Assim, teremos armazenado em post_data algo como name=Ricardo&surname=Santos&office=Developer&suggestion=Mais+café.


E então, parseamos esses dados para usá-los mais adiante.

#!/usr/bin/python3

import csv
import os
import sys

print("Content-type: text/html\n")

if os.environ['REQUEST_METHOD'] == 'POST':
    content_length = int(os.environ['CONTENT_LENGTH']
    post_data = sys.stdin.read(content_length)
    
    params = post_data.split('&')
    form_data = {}

    for param in params:
        key, value = param.split('=')
        form_data[key] = value

    name = form_data['name']
    surname = form_data['surname']
    office = form_data['office']
    suggestion = form_data['suggestion']
else:
    print("<html><body><h1>Método não permitido. Use POST.</h1></body></html>")

E por último vamos salvar os dados desse formulário em uma planilha no formato csv.

#!/usr/bin/python3

import csv
import os
import sys

print("Content-type: text/html\n")

if os.environ['REQUEST_METHOD'] == 'POST':
    content_length = int(os.environ['CONTENT_LENGTH']
    post_data = sys.stdin.read(content_length)
    
    params = post_data.decode('utf-8').split('&')
    form_data = {}

    for param in params:
        key, value = param.split('=')
        form_data[key] = value

    name = form_data['name']
    surname = form_data['surname']
    office = form_data['office']
    suggestion = form_data['suggestion']

     with open('/var/www/html/sugestoes.csv', 'a', newline='') as csvfile:
        fieldnames = ['Nome', 'Sobrenome', 'Cargo', 'Sugestao']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        
        if csvfile.tell() == 0:
            writer.writeheader()
        
        writer.writerow({'Nome': name, 'Sobrenome': surname, 'Cargo': office, 'Sugestao': suggestion})
        print("<html><body><h1>Obrigado pela sua sugestão!</h1></body></html>")
else:
    print("<html><body><h1>Método não permitido. Use POST.</h1></body></html>")

Antes de testar, não se esqueça de converter o script em um executável. Se você tiver algum problema de permissões, execute o comando abaixo para conceder essas permissões ao Apache.

sudo chmod +x /var/www/html/process_form.py
sudo chown -R www-data:www-data /var/www/html/
sudo chmod -R 775 /var/www/html/

Conclusão

O CGI foi amplamente utilizado no passado, mas hoje em dia não é recomendado para tarefas complexas. Isso ocorre porque o padrão CGI consome muitos recursos, já que um novo processo é aberto para cada requisição. Atualmente, existem métodos mais eficientes para gerar conteúdo dinâmico. Pretendo escrever novas versões deste post para abordar essas formas mais modernas e eficientes.

Embora este método seja bastante simples, ele é essencial para quem deseja aprender e trabalhar com arquiteturas de micro-serviços. Compreender os fundamentos do CGI pode fornecer uma base sólida para entender como as requisições e respostas funcionam na web. Além disso, ao dominar esses conceitos básicos, você estará melhor preparado para avançar para tecnologias mais avançadas e eficientes, como FastCGI, WSGI, ou até mesmo frameworks modernos como Flask e Django, que são amplamente utilizados para desenvolver aplicações web robustas e escaláveis.

Portanto, mesmo que o CGI não seja a melhor escolha para projetos complexos, aprender sobre ele pode ser um passo importante na sua jornada como desenvolvedor.

Carregando publicação patrocinada...
3

Muito bom. Foi exatamente assim que fiz minha primeira aplicação web em python, ainda na versão 2. Concordo plenamente, mesmo cgi sendo "obseleto" todo desenvolvedor backend tem obrigação de conhecer, antes de se aventurar com os frameworks!

2

Obrigado por compartilhar algo que custou um razoável esforço de aprendizagem e doce labuta profissional. Já usei o CGI há muito tempo, mas achava ultrapassado, graças a seu artigo vislumbrei possibilidades de uso em cenários justamente iguais aos relatados por você. Artigo primoroso!

1
1

Embora seja algo obsoleto existem vários servidores com cPanel que disponibilizam o acesso ao CGI porque vários scripts dentro do cPanel utilizam este recurso.

Se não me engano o PHP surgiu apartir do CGI...