🌐 Web e HTTP para Backend Python — Notas

HTTP  |  REST  |  APIs  |  FastAPI  |  Autenticação

HTTP — Como a Web Funciona

O HTTP (HyperText Transfer Protocol) é o protocolo de comunicação da web. É baseado em requisição e resposta — o cliente pede, o servidor responde. Como backend Python, você cria o servidor que processa essas requisições.

Fluxo de uma Requisição HTTP
Cliente (app, curl, browser) ↓ 1. DNS resolve "api.exemplo.com" → IP ↓ 2. TCP handshake (conexão) ↓ 3. Envia requisição HTTP Servidor (sua API Python) ↓ 4. Processa a requisição ↓ 5. Consulta banco, aplica lógica ↓ 6. Retorna resposta HTTP Cliente ↓ 7. Processa a resposta (JSON, HTML...)
Métodos HTTP
MétodoAçãoTem body?Idempotente?Uso típico
GETBuscar/ler dadosNãoSimListar usuários, buscar produto
POSTCriar novo recursoSimNãoCriar usuário, fazer login
PUTAtualizar completoSimSimSubstituir recurso inteiro
PATCHAtualizar parcialSimNãoAtualizar só o nome de um usuário
DELETERemover recursoNãoSimDeletar usuário, remover item
⚠ Idempotência
Requisição idempotente = chamar N vezes tem o mesmo efeito que chamar 1 vez. GET busca sempre o mesmo dado. DELETE deleta uma vez, chamar de novo não muda nada. POST cria um novo registro a cada chamada — não é idempotente.
Códigos de Status HTTP
FaixaSignificadoExemplos importantes
2xx — SucessoRequisição processada com sucesso200 OK, 201 Created, 204 No Content
3xx — RedirecionamentoCliente deve seguir outro endereço301 Moved, 302 Found, 304 Not Modified
4xx — Erro do ClienteProblema na requisição enviada400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 422 Unprocessable
5xx — Erro do ServidorProblema no servidor ao processar500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable
Status Codes mais usados em APIs
200 — sucesso com body de resposta (GET, PUT, PATCH)
201 — recurso criado com sucesso (POST)
204 — sucesso sem body (DELETE)
400 — dados inválidos enviados pelo cliente
401 — não autenticado (precisa de token/login)
403 — autenticado mas sem permissão
404 — recurso não encontrado
500 — erro interno — nunca exponha detalhes ao cliente!
Headers HTTP
# Headers comuns em APIs REST Content-Type: application/json # formato do body enviado Accept: application/json # formato esperado na resposta Authorization: Bearer eyJhbG... # token de autenticação JWT X-Request-ID: abc123 # ID único da requisição (rastreamento) Cache-Control: no-cache # controle de cache # Headers de CORS (Cross-Origin Resource Sharing) Access-Control-Allow-Origin: * # permite qualquer origem Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Exercício 1: Qual a diferença entre 401 e 403?
401: não autenticado (sem token / token inválido). 403: autenticado mas sem permissão.
401 = "Quem é você?" → faça login. 403 = "Sei quem você é, mas não pode fazer isso" → usuário comum tentando acessar rota de admin.
REST e Design de APIs

REST (Representational State Transfer) é um estilo arquitetural para APIs web. Define princípios de como organizar rotas, usar métodos HTTP e estruturar respostas.

Princípios REST
Stateless (sem estado)
Cada requisição contém toda informação necessária. O servidor não guarda estado da sessão. O cliente envia o token em cada requisição.
Recursos e URIs
Recursos são identificados por URIs. Use substantivos no plural, não verbos.
Bom: /usuarios, /produtos/42
Ruim: /getUsuario, /deletarProduto
Representações
Recursos são transferidos em formatos como JSON (padrão atual), XML (legado). O cliente especifica o formato com o header Accept.
Interface Uniforme
Mesmos métodos HTTP para todos os recursos. GET sempre lê, POST sempre cria, etc. Consistência facilita o uso da API.
Design de Rotas REST
OperaçãoMétodoRotaBodyResposta
Listar todosGET/usuarios200 + lista
Buscar umGET/usuarios/42200 + objeto
CriarPOST/usuariosJSON201 + criado
Atualizar tudoPUT/usuarios/42JSON completo200 + atualizado
Atualizar parcialPATCH/usuarios/42JSON parcial200 + atualizado
DeletarDELETE/usuarios/42204
JSON — Formato padrão de APIs
// Resposta típica de uma API REST { "id": 42, "nome": "Ana Silva", "email": "ana@exemplo.com", "criado_em": "2024-01-15T10:30:00Z", "ativo": true, "endereco": { "cidade": "Campo Grande", "uf": "MS" }, "tags": ["admin", "premium"] } // Resposta de erro padronizada { "erro": "USUARIO_NAO_ENCONTRADO", "mensagem": "Usuário com ID 42 não existe", "status": 404, "timestamp": "2024-01-15T10:30:00Z" }
⚠ Versionamento de API
Sempre versione sua API para não quebrar clientes existentes ao evoluir. Ex: /api/v1/usuarios, /api/v2/usuarios. Quando mudar a estrutura de resposta, crie v2 mantendo v1 funcionando por um período de deprecação.
Exercício 2: Por que usar /produtos e não /getProdutos na rota REST?
→ O método HTTP já define a ação. A URI identifica o recurso, não a operação.
GET /produtos = listar. POST /produtos = criar. O verbo na URL é redundante e vai contra o princípio REST de interface uniforme.
FastAPI — Backend Python Moderno

FastAPI é o framework Python mais moderno para APIs REST. É rápido (async nativo), tem validação automática com Pydantic e gera documentação Swagger automaticamente.

pip install fastapi uvicorn[standard]
API Completa com FastAPI
from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel from typing import Optional app = FastAPI(title="API de Usuários", version="1.0.0") # Schema de validação (Pydantic) class UsuarioCriar(BaseModel): nome: str email: str idade: Optional[int] = None class UsuarioResposta(BaseModel): id: int nome: str email: str # Banco de dados simulado db: dict[int, dict] = {} proximo_id = 1 # GET /usuarios — listar todos @app.get("/usuarios", response_model=list[UsuarioResposta]) def listar_usuarios(): return list(db.values()) # GET /usuarios/{id} — buscar um @app.get("/usuarios/{usuario_id}", response_model=UsuarioResposta) def buscar_usuario(usuario_id: int): if usuario_id not in db: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Usuário {usuario_id} não encontrado" ) return db[usuario_id] # POST /usuarios — criar @app.post("/usuarios", response_model=UsuarioResposta, status_code=status.HTTP_201_CREATED) def criar_usuario(usuario: UsuarioCriar): global proximo_id novo = {"id": proximo_id, **usuario.dict()} db[proximo_id] = novo proximo_id += 1 return novo # DELETE /usuarios/{id} @app.delete("/usuarios/{usuario_id}", status_code=204) def deletar_usuario(usuario_id: int): if usuario_id not in db: raise HTTPException(status_code=404, detail="Não encontrado") del db[usuario_id] # Rodar: uvicorn main:app --reload # Docs: http://localhost:8000/docs (Swagger automático!)
Query Params, Path Params e Body
# Path param — parte da URL: /produtos/42 @app.get("/produtos/{produto_id}") def buscar_produto(produto_id: int): # FastAPI valida e converte o tipo ... # Query param — após ?: /produtos?pagina=2&limite=10 @app.get("/produtos") def listar_produtos(pagina: int = 1, limite: int = 10, busca: str = ""): ... # Body — JSON no corpo da requisição (POST/PUT/PATCH) @app.post("/produtos") def criar_produto(produto: ProdutoSchema): # Pydantic valida automaticamente ...
Middleware e Dependências
from fastapi import Depends from fastapi.middleware.cors import CORSMiddleware # CORS — permitir frontend acessar a API app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], allow_methods=["*"], allow_headers=["*"], ) # Dependency Injection — reutilizar lógica entre rotas def obter_db(): db = SessionLocal() try: yield db finally: db.close() @app.get("/usuarios") def listar(db=Depends(obter_db)): return db.query(Usuario).all()
Exercício 3: Por que FastAPI gera documentação automática?
→ Usa os type hints do Python e schemas Pydantic para gerar OpenAPI spec automaticamente.
Ao declarar tipos nos parâmetros e usar BaseModel, FastAPI sabe exatamente o formato esperado e gera o Swagger UI em /docs sem nenhuma configuração extra.
Autenticação e Segurança em APIs
JWT — JSON Web Token

JWT é o padrão de autenticação mais usado em APIs REST. O servidor gera um token assinado após o login — o cliente envia esse token em cada requisição.

Estrutura do JWT: header.payload.signature Header: {"alg": "HS256", "typ": "JWT"} Payload: {"sub": "42", "nome": "Ana", "exp": 1716000000} Signature: HMACSHA256(base64(header)+"."+base64(payload), secret) # No header da requisição: Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.abc123
pip install python-jose[cryptography] passlib[bcrypt] from jose import JWTError, jwt from passlib.context import CryptContext from datetime import datetime, timedelta SECRET_KEY = "sua-chave-secreta-muito-longa" ALGORITHM = "HS256" EXPIRA_EM = 30 # minutos pwd_context = CryptContext(schemes=["bcrypt"]) def hash_senha(senha: str) -> str: return pwd_context.hash(senha) def verificar_senha(senha: str, hash: str) -> bool: return pwd_context.verify(senha, hash) def criar_token(dados: dict) -> str: dados["exp"] = datetime.utcnow() + timedelta(minutes=EXPIRA_EM) return jwt.encode(dados, SECRET_KEY, algorithm=ALGORITHM) def verificar_token(token: str) -> dict: try: return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) except JWTError: raise HTTPException(status_code=401, detail="Token inválido")
Fluxo de Autenticação JWT
1. POST /auth/login {email, senha} ↓ servidor verifica senha (bcrypt) ↓ gera JWT com user_id e exp ← 200 {access_token: "eyJ..."} 2. GET /usuarios/perfil → Header: Authorization: Bearer eyJ... ↓ servidor valida JWT (assinatura + expiração) ↓ extrai user_id do payload ← 200 {id: 42, nome: "Ana"}
Boas Práticas de Segurança em APIs
ProblemaSolução
Senhas em texto puroSempre hash com bcrypt — nunca armazene senha pura
Token sem expiraçãoJWT com exp curto (15-30min) + refresh token
SQL InjectionUsar ORM (SQLAlchemy) ou queries parametrizadas
Dados sensíveis na URLDados sensíveis no body (POST), não em query params
CORS aberto demaisallow_origins com domínios específicos em produção
Erros com stack traceEm produção, retornar mensagem genérica ao cliente
Rate limit inexistenteLimitar requisições por IP (slowapi no FastAPI)
SQLAlchemy — ORM para Python
pip install sqlalchemy from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.orm import DeclarativeBase, Session engine = create_engine("sqlite:///./banco.db") class Base(DeclarativeBase): pass class Usuario(Base): __tablename__ = "usuarios" id = Column(Integer, primary_key=True) nome = Column(String(100), nullable=False) email = Column(String(100), unique=True) Base.metadata.create_all(engine) # cria tabelas # CRUD com sessão with Session(engine) as session: # Create usuario = Usuario(nome="Ana", email="ana@ex.com") session.add(usuario) session.commit() # Read ana = session.query(Usuario).filter_by(email="ana@ex.com").first() # Update ana.nome = "Ana Silva" session.commit() # Delete session.delete(ana) session.commit()
⚠ ORM vs SQL puro
ORM (SQLAlchemy): mais seguro, mais produtivo, independente de banco. SQL puro: mais performance em queries complexas. Recomendação: use ORM por padrão, SQL puro só quando necessário por performance.
Exercício 4: Por que nunca armazenar senhas em texto puro?
→ Se o banco vazar, todas as senhas são expostas. Com bcrypt, o atacante só vê o hash — irreversível.
bcrypt é lento por design (fator de custo configurável) — dificulta ataques de força bruta. Sempre: hash = bcrypt(senha). Login: bcrypt.verify(senha_digitada, hash_armazenado).
📝 Cola — Web e HTTP para Backend Python
Métodos HTTP × CRUD
CRUDMétodo HTTPStatus sucessoExemplo
Read (listar)GET200GET /usuarios
Read (um)GET200GET /usuarios/42
CreatePOST201POST /usuarios
Update (total)PUT200PUT /usuarios/42
Update (parcial)PATCH200PATCH /usuarios/42
DeleteDELETE204DELETE /usuarios/42
Status Codes Essenciais
CodeSignificadoQuando usar
200OKGET, PUT, PATCH com sucesso
201CreatedPOST com sucesso
204No ContentDELETE com sucesso
400Bad RequestDados inválidos no body
401UnauthorizedSem token / token inválido
403ForbiddenSem permissão
404Not FoundRecurso não existe
422UnprocessableValidação falhou (FastAPI padrão)
500Server ErrorBug no servidor
⭐ Regras de Ouro
Regra
URI = substantivo plural. Método = verbo. GET /usuarios, nunca /getUsuarios
Regra
401 = não autenticado. 403 = autenticado mas sem permissão
Regra
Senhas sempre com bcrypt — nunca texto puro, nunca MD5/SHA1
Regra
JWT no header: Authorization: Bearer token — nunca na URL
Regra
REST é stateless — servidor não guarda sessão. Token em cada requisição
RegraPOST cria = 201. DELETE sem body = 204. GET/PUT bem sucedido = 200
Regra
ORM protege de SQL Injection. Nunca concatenar string em query SQL
Regra
FastAPI: /docs = Swagger automático. Uvicorn serve a API. Pydantic valida automaticamente