Programação Distribuída com Redes usando Linux e Python
Socket, TCP/UDP, HTTP e TLS em Python: o que fazer, código, comando de teste, evidência de screenshot e explicação curta para fechar a resposta com segurança.
Este material foi estruturado para evitar o padrão que mais derruba nota em disciplinas práticas: código que roda, mas sem prova clara e sem explicação objetiva. A estratégia aqui é padronizar cada exercício em cinco peças fixas. Com isso, você transforma uma sequência longa (socket básico, TCP, UDP, HTTP manual, HTTP com framework e TLS) em um roteiro único e repetível.
O objetivo não é "escrever muito". O objetivo é responder com precisão, em formato auditável. Quando você sempre apresenta implementação, teste e evidência na mesma ordem, o avaliador consegue validar rápido e você reduz risco de esquecer partes essenciais.
| Peça obrigatória | O que entra |
|---|---|
| 1) O que foi feito | Resumo em 1-3 frases do objetivo técnico do exercício. |
| 2) Código | Arquivo .py com implementação mínima funcional. |
| 3) Como foi testado | Comandos exatos executados no terminal. |
| 4) Evidência para PDF | Recorte curto de screenshot com sinal técnico relevante. |
| 5) O que a evidência prova | 1 frase de conclusão + 2-4 linhas quando o enunciado pedir explicação. |
Pré-voo (2 minutos)
python3 --version
command -v nc
command -v curl
command -v openssl
command -v ss
ss -tlnp | egrep ':(5000|6000|8080|8443)\b' || true
mkdir -p tp3_socket && cd tp3_socketss -tlnp/ss -ulnp não mostrar processo/PID, rode com sudo. O LISTEN já prova porta aberta; o PID prova o "dono" do socket.Checklist transversal (vale para todo exercício com servidor)
- Provar
LISTENquando houver serviço. - Provar funcionalidade de ponta a ponta com cliente.
- Capturar status/headers/bytes quando o protocolo for HTTP.
- Anexar evidência curta e legível, sem dump gigante.
- Fechar com uma frase objetiva do tipo "isso prova que...".
Glossário mínimo (para explicar com propriedade)
| Termo | Leitura prática no TP3 |
|---|---|
| Endpoint | Par IP:porta (ex.: 127.0.0.1:8080). |
| TCP (stream) | recv() lê bytes que chegaram agora; não existe "mensagem" implícita. |
| UDP (datagrama) | recvfrom() opera por datagrama, sem garantia de entrega/ordem fim a fim. |
| CRLF em HTTP | Headers terminam em \r\n\r\n; depois disso começa o corpo. |
| Content-Length | Quantidade de bytes do corpo; em POST manual, você precisa ler exatamente esse tamanho. |
| TLS | Canal criptografado sob HTTP; HTTPS = HTTP sobre TLS. |
socket com mini-prova executável.Conceito cobrado
AF_INETeAF_INET6definem formato de endereço (IPv4/IPv6).SOCK_STREAMusa TCP (confiável e ordenado);SOCK_DGRAMusa UDP (sem garantia fim a fim).- Servidor TCP clássico:
bind->listen->accept. - Porta 0 delega ao SO a escolha de uma porta livre.
# ex1_porta0.py
import socket
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("0.0.0.0", 0))
print("Porta escolhida:", s.getsockname()[1])
s.close()
if __name__ == "__main__":
main()Como testar
python3 ex1_porta0.pygetaddrinfo para TCPexample.com:80 e interpretar cada campo da tupla retornada.getaddrinfo com AF_UNSPEC + SOCK_STREAM e explicar family, type, proto, canonname e sockaddr.# ex2_getaddrinfo.py
import socket
def main():
host = "example.com"
port = 80
infos = socket.getaddrinfo(host, port, family=socket.AF_UNSPEC, type=socket.SOCK_STREAM)
print(f"Total de resultados: {len(infos)}")
for i, (family, socktype, proto, canonname, sockaddr) in enumerate(infos, 1):
fam = "AF_INET" if family == socket.AF_INET else "AF_INET6" if family == socket.AF_INET6 else str(family)
st = "SOCK_STREAM" if socktype == socket.SOCK_STREAM else str(socktype)
print(f"[{i}] family={fam} socktype={st} proto={proto} canonname={canonname!r} sockaddr={sockaddr}")
if __name__ == "__main__":
main()Como testar
python3 ex2_getaddrinfo.pyComo explicar a saída
family: IPv4 ou IPv6.socktype: tipo de transporte (aqui, TCP).proto: número de protocolo associado.canonname: nome canônico (pode vir vazio).sockaddr: tupla pronta paraconnect.
# ex3_timeout.py
import socket
def main():
addr = ("192.0.2.1", 12345) # TEST-NET-1
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2.0)
try:
s.connect(addr)
print("conectou (inesperado)")
except socket.timeout:
print("timeout")
finally:
s.close()
if __name__ == "__main__":
main()Como testar
python3 ex3_timeout.pytimeout quando o ambiente permitir.htons e struct.pack("!H")# ex4_endian.py
import socket
import struct
import sys
def main():
port = 8080
print("Host byteorder:", sys.byteorder)
print("8080 (host int):", port)
print("htons(8080):", socket.htons(port))
packed = struct.pack("!H", port)
print("struct.pack('!H',8080) bytes:", packed)
print("unpack ->", struct.unpack("!H", packed)[0])
if __name__ == "__main__":
main()Como testar
python3 ex4_endian.pyhtons converte inteiro de host order para network order; o prefixo ! no struct fixa big-endian.0.0.0.0:5000# ex5_tcp_echo_server.py
import socket
HOST = "0.0.0.0"
PORT = 5000
def main():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(50)
print(f"TCP echo server em {HOST}:{PORT} (Ctrl+C para sair)")
try:
while True:
conn, addr = srv.accept()
print("Cliente conectado:", addr)
try:
data = conn.recv(1024)
if data:
conn.sendall(data)
finally:
conn.close()
print("Cliente finalizado:", addr)
except KeyboardInterrupt:
print("Encerrando por KeyboardInterrupt")
finally:
srv.close()
if __name__ == "__main__":
main()Como testar (2 terminais)
# Terminal 1
python3 ex5_tcp_echo_server.py
ss -tlnp | grep ':5000\b' || true
# Terminal 2
nc 127.0.0.1 5000
# digite uma linha e Enterss -tlnp, execute com sudo para provar também o processo dono da porta.- Screenshot do servidor com "Cliente conectado".
- Screenshot do cliente
ncmostrando eco do texto. - Opcional: screenshot de
LISTENna porta 5000.
PING.# ex6_tcp_client_retry.py
import socket
import time
import sys
HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
PORT = 5000
MSG = b"PING\n"
def main():
attempts = 3
for i in range(1, attempts + 1):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2.0)
try:
s.connect((HOST, PORT))
s.sendall(MSG)
data = s.recv(1024)
print(data.decode("utf-8", errors="replace"), end="")
return
except (OSError, socket.timeout) as e:
print(f"tentativa {i}/{attempts} falhou: {e}")
if i < attempts:
time.sleep(1.0)
finally:
s.close()
print("desistindo após 3 tentativas")
if __name__ == "__main__":
main()Como testar
# com o servidor do Ex.05 rodando
python3 ex6_tcp_client_retry.py 127.0.0.1PING (eco recebido).0.0.0.0:6000 com cliente de três mensagens# ex7_udp_server.py
import socket
HOST = "0.0.0.0"
PORT = 6000
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((HOST, PORT))
print(f"UDP server em {HOST}:{PORT}")
while True:
data, addr = s.recvfrom(2048)
s.sendto(b"OK:" + data, addr)
if __name__ == "__main__":
main()# ex7_udp_client.py
import socket
HOST = "127.0.0.1"
PORT = 6000
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for m in ["um", "dois", "tres"]:
s.sendto(m.encode("utf-8"), (HOST, PORT))
data, _ = s.recvfrom(2048)
print(data.decode("utf-8", errors="replace"))
s.close()
if __name__ == "__main__":
main()Como testar
# Terminal 1
python3 ex7_udp_server.py
ss -ulnp | grep ':6000\b' || true
# Terminal 2
python3 ex7_udp_client.pyss -ulnp, execute com sudo para mapear o processo dono do socket UDP.Explicação curta pronta
- UDP pode perder pacotes porque não existe retransmissão garantida fim a fim.
- UDP não garante ordem porque cada datagrama é independente no caminho de rede.
OK:um, OK:dois, OK:tres.# ex8_tcp_echo_threaded.py
import socket
import threading
HOST = "0.0.0.0"
PORT = 5000
def handle_client(conn, addr):
try:
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
finally:
conn.close()
print("Cliente saiu:", addr)
def main():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(50)
print(f"TCP echo threaded em {HOST}:{PORT} (Ctrl+C para sair)")
try:
while True:
conn, addr = srv.accept()
print("Cliente conectou:", addr)
t = threading.Thread(target=handle_client, args=(conn, addr), daemon=True)
t.start()
except KeyboardInterrupt:
print("Encerrando")
finally:
srv.close()
if __name__ == "__main__":
main()Como testar (3 terminais)
# Terminal 1
python3 ex8_tcp_echo_threaded.py
ss -tlnp | grep ':5000\b' || true
# Terminal 2
nc 127.0.0.1 5000
# Terminal 3
nc 127.0.0.1 5000sudo, algumas distros ocultam PID/processo no ss -tlnp.:8080 com resposta hello world!\r\n\r\n; Content-Length precisa bater com o body.:8080, encerre o servidor anterior para evitar Address already in use.# ex9_http_hello.py
import socket
HOST = "0.0.0.0"
PORT = 8080
def recv_until(conn, marker=b"\r\n\r\n", limit=8192):
data = b""
while marker not in data:
chunk = conn.recv(1024)
if not chunk:
break
data += chunk
if len(data) > limit:
break
return data
def main():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(50)
print(f"HTTP manual em {HOST}:{PORT}")
body = b"hello world!"
headers = (
b"HTTP/1.1 200 OK\r\n"
b"Content-Type: text/plain; charset=utf-8\r\n"
b"Content-Length: 12\r\n"
b"Connection: close\r\n"
b"\r\n"
)
resp = headers + body
while True:
conn, _ = srv.accept()
try:
_ = recv_until(conn)
conn.sendall(resp)
finally:
conn.close()
if __name__ == "__main__":
main()Como testar
python3 ex9_http_hello.py
ss -tlnp | grep ':8080\b' || true
curl -v http://127.0.0.1:8080/ss -tlnp não mostrar PID, rode com sudo para evidenciar o processo do listener.curl -v exibindo status, headers e body.index.html no / e responder 404 fora da rotaContent-Length correto em respostas 200/404.# preparo do arquivo
printf '<h1>OK</h1>\n' > index.html# ex10_http_files_404.py
import socket
import os
HOST = "0.0.0.0"
PORT = 8080
def recv_until(conn, marker=b"\r\n\r\n", limit=16384):
data = b""
while marker not in data:
chunk = conn.recv(1024)
if not chunk:
break
data += chunk
if len(data) > limit:
break
return data
def parse_request_line(req_bytes):
line = req_bytes.split(b"\r\n", 1)[0].decode("iso-8859-1", errors="replace")
parts = line.split()
if len(parts) >= 2:
return parts[0], parts[1]
return None, None
def send_response(conn, status_line, content_type, body_bytes):
hdr = (
f"HTTP/1.1 {status_line}\r\n"
f"Content-Type: {content_type}\r\n"
f"Content-Length: {len(body_bytes)}\r\n"
f"Connection: close\r\n"
f"\r\n"
).encode("utf-8")
conn.sendall(hdr + body_bytes)
def main():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(50)
print(f"HTTP manual em {HOST}:{PORT}")
while True:
conn, _ = srv.accept()
try:
req = recv_until(conn)
method, path = parse_request_line(req)
if method != "GET":
send_response(conn, "405 Method Not Allowed", "text/plain; charset=utf-8", b"Method Not Allowed")
continue
if path == "/":
if os.path.exists("index.html"):
body = open("index.html", "rb").read()
send_response(conn, "200 OK", "text/html; charset=utf-8", body)
else:
send_response(conn, "500 Internal Server Error", "text/plain; charset=utf-8", b"index.html missing")
else:
send_response(conn, "404 Not Found", "text/plain; charset=utf-8", b"Not Found")
finally:
conn.close()
if __name__ == "__main__":
main()Como testar
python3 ex10_http_files_404.py
curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/naoexiste/ e 404 fora da rota), ambos com Content-Length coerente.GET /status e POST /echo (até 10 KiB)Content-Length e consumir corpo exatamente no tamanho informado.# ex11_http_routes.py
import datetime
import json
import socket
HOST = "0.0.0.0"
PORT = 8080
MAX_BODY = 10 * 1024
def recv_headers(conn, marker=b"\r\n\r\n", limit=65536):
data = b""
while marker not in data:
chunk = conn.recv(1024)
if not chunk:
break
data += chunk
if len(data) > limit:
break
parts = data.split(marker, 1)
if len(parts) == 2:
return parts[0], parts[1]
return data, b""
def parse_request(head_bytes):
lines = head_bytes.decode("iso-8859-1", errors="replace").split("\r\n")
request_line = lines[0] if lines else ""
pieces = request_line.split()
method = pieces[0] if len(pieces) >= 1 else ""
path = pieces[1] if len(pieces) >= 2 else ""
headers = {}
for line in lines[1:]:
if ":" in line:
k, v = line.split(":", 1)
headers[k.strip().lower()] = v.strip()
return method, path, headers
def read_body(conn, body_start, content_length):
body = body_start
while len(body) < content_length:
remaining = content_length - len(body)
chunk = conn.recv(4096 if remaining > 4096 else remaining)
if not chunk:
break
body += chunk
return body[:content_length]
def send_response(conn, status_line, content_type, body):
hdr = (
f"HTTP/1.1 {status_line}\r\n"
f"Content-Type: {content_type}\r\n"
f"Content-Length: {len(body)}\r\n"
f"Connection: close\r\n"
f"\r\n"
).encode("utf-8")
conn.sendall(hdr + body)
def main():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(50)
print(f"HTTP rotas em {HOST}:{PORT}")
while True:
conn, _ = srv.accept()
try:
head, body_start = recv_headers(conn)
method, path, headers = parse_request(head)
if method == "GET" and path == "/status":
payload = {
"status": "ok",
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
}
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
send_response(conn, "200 OK", "application/json; charset=utf-8", body)
continue
if method == "POST" and path == "/echo":
length = int(headers.get("content-length", "0") or "0")
if length > MAX_BODY:
send_response(conn, "413 Payload Too Large", "text/plain; charset=utf-8", b"Payload Too Large")
continue
body = read_body(conn, body_start, length)
send_response(conn, "200 OK", "text/plain; charset=utf-8", body)
continue
send_response(conn, "404 Not Found", "text/plain; charset=utf-8", b"Not Found")
finally:
conn.close()
if __name__ == "__main__":
main()Como testar
python3 ex11_http_routes.py
curl -i http://127.0.0.1:8080/status
curl -i -X POST --data 'abc' http://127.0.0.1:8080/echo
curl -i http://127.0.0.1:8080/naoexisteComo explicar o parsing (texto pronto)
- A linha de requisição (
METHOD PATH HTTP/...) é lida no cabeçalho. - Os headers são consumidos até o delimitador
\r\n\r\n. - O corpo não pode ser lido "até acabar"; ele deve ser lido em loop conforme
Content-Length. - Sem essa disciplina, o servidor pode truncar payload ou travar esperando bytes que não virão.
Teste extra obrigatório do limite (10 KiB)
# gerar payload acima de 10 KiB e validar 413
python3 - <<'PY' > payload_11k.txt
print("a" * (11 * 1024))
PY
curl -i -X POST --data-binary @payload_11k.txt http://127.0.0.1:8080/echo/status (200 JSON), /echo (200 com corpo), rota inválida (404) e payload acima do limite (413).http.server e comparação objetiva# ex12_httpserver.py
from http.server import BaseHTTPRequestHandler, HTTPServer
import datetime
import json
HOST = "0.0.0.0"
PORT = 8080
MAX_BODY = 10 * 1024
class Handler(BaseHTTPRequestHandler):
def _send(self, status, ctype, body):
self.send_response(status)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/status":
payload = {
"status": "ok",
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
}
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
self._send(200, "application/json; charset=utf-8", body)
return
self._send(404, "text/plain; charset=utf-8", b"Not Found")
def do_POST(self):
if self.path != "/echo":
self._send(404, "text/plain; charset=utf-8", b"Not Found")
return
length = int(self.headers.get("Content-Length", "0") or "0")
if length > MAX_BODY:
self._send(413, "text/plain; charset=utf-8", b"Payload Too Large")
return
body = self.rfile.read(length)
self._send(200, "text/plain; charset=utf-8", body)
def log_message(self, fmt, *args):
return
def main():
server = HTTPServer((HOST, PORT), Handler)
print(f"http.server em {HOST}:{PORT}")
server.serve_forever()
if __name__ == "__main__":
main()Como testar
python3 ex12_httpserver.py
curl -i http://127.0.0.1:8080/status
curl -i -X POST --data 'abc' http://127.0.0.1:8080/echo
curl -i http://127.0.0.1:8080/naoexisteComparação técnica (o que muda em qualidade e risco)
- Parsing/edge cases: no manual, você controla tudo, mas também assume o risco de errar delimitação de headers/corpo e casos limítrofes.
- Segurança/hardening: no manual, qualquer proteção extra depende de você; no
http.serverjá há parsing consolidado, mas ainda não é um stack completo de produção. - Performance: ambos atendem laboratório; para alto volume, o ponto principal é arquitetura/servidor real (workers, timeout, limits, observabilidade), não só a API usada.
- Extensibilidade: manual facilita estudar protocolo;
http.serveracelera evolução de rotas sem reimplementar toda base HTTP. - Decisão prática no TP: Ex.11 prova domínio de protocolo; Ex.12 prova maturidade de engenharia ao escolher abstração mais segura para manter.
- Nota importante:
http.serverserve muito bem para estudo e ferramentas internas, mas não deve ser tratado como servidor de produção por padrão.
key.pem e cert.pem para ambiente local com CN=localhost e validade de 30 dias.openssl req -x509 -newkey rsa:2048 -nodes \
-keyout key.pem \
-out cert.pem \
-days 30 \
-subj "/CN=localhost"
ls -lh key.pem cert.pemExplicação curta obrigatória (texto pronto)
key.pemé a chave privada do servidor: ela assina o handshake e nunca deve ser exposta.cert.pemé o certificado público apresentado ao cliente, contendo identidade e chave pública.- Em certificado autoassinado, o emissor não é uma CA pública confiável para navegador/curl por padrão.
- Por isso, no laboratório, usa-se
-knocurlpara ignorar validação de cadeia de confiança. - Para ambientes mais realistas, inclua SAN (Subject Alternative Name), pois clientes modernos validam SAN e não apenas CN.
ls -lh mostrando os dois arquivos.:8443 encapsulando HTTP mínimocurl -vk.:8443, encerre o processo anterior para evitar conflito de bind.# ex14_tls_http_hello.py
import socket
import ssl
HOST = "0.0.0.0"
PORT = 8443
def recv_until(conn, marker=b"\r\n\r\n", limit=8192):
data = b""
while marker not in data:
chunk = conn.recv(1024)
if not chunk:
break
data += chunk
if len(data) > limit:
break
return data
def main():
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(50)
print(f"TLS HTTP em {HOST}:{PORT}")
body = b"hello world!"
headers = (
b"HTTP/1.1 200 OK\r\n"
b"Content-Type: text/plain; charset=utf-8\r\n"
b"Content-Length: 12\r\n"
b"Connection: close\r\n"
b"\r\n"
)
resp = headers + body
while True:
conn, _ = srv.accept()
try:
tls_conn = context.wrap_socket(conn, server_side=True)
_ = recv_until(tls_conn)
tls_conn.sendall(resp)
tls_conn.close()
except ssl.SSLError as e:
print("TLS erro:", e)
conn.close()
if __name__ == "__main__":
main()Como testar (prévia)
python3 ex14_tls_http_hello.py
ss -tlnp | grep ':8443\b' || truess -tlnp, rode com sudo para provar também o dono do socket TLS.curl -vk.curl -vk --resolvecurl -vk --resolve localhost:8443:127.0.0.1 https://localhost:8443/O que capturar na evidência
- Trecho do handshake mostrando versão/cifra negociada.
- Status HTTP 200.
- Body
hello world!.
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers("HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES:!DES:!EXP:!PSK:!SRP:!DSS")Prova mínima
curl -vk --resolve localhost:8443:127.0.0.1 https://localhost:8443/Prova forte (opcional)
openssl s_client -connect 127.0.0.1:8443 -servername localhost -tls1_2Checklist final de entrega (produto auditável)
- Cada exercício contém código executável em arquivo próprio.
- Comandos de teste estão explícitos e reproduzíveis.
- Evidência é curta e mostra o sinal certo (LISTEN, status, handshake, saída de cliente), sem poluição de terminal.
- Cada item fecha com frase clara do que foi provado.
- Exercícios 14-16 mostram TLS funcionando e versão/cifra compatível com o requisito.
- Nome do PDF segue exatamente a convenção exigida no enunciado (mesma grafia, separadores e extensão).
- Há pelo menos uma evidência por requisito técnico (ex.: LISTEN, teste funcional com cliente, validação HTTP/TLS).
Padrão de recorte de evidência para o PDF
- Capturar só o trecho que comprova o requisito (comando + saída essencial).
- Evitar screenshots muito longos com informação irrelevante.
- Quando houver mais de um requisito no exercício, anexar evidências separadas e legíveis.
Erros comuns por tema (sinal, causa provável e correção)
Sockets/TCP/UDP
- Sinal:
Address already in use| Causa provável: porta ocupada | Como provar/corrigir:ss -tlnp/ss -ulnpe trocar porta ou finalizar processo. - Sinal: cliente não conecta | Causa provável: servidor não está em LISTEN | Como provar/corrigir: validar
LISTENantes do teste cliente. - Sinal: eco truncado | Causa provável: leitura/escrita parcial sem loop | Como provar/corrigir: revisar
recv/sendalle repetir teste com payload conhecido.
HTTP Manual
- Sinal: body incompleto em POST | Causa provável: leitura incorreta do
Content-Length| Como provar/corrigir: ler em loop até o total de bytes. - Sinal: resposta quebrada no cliente | Causa provável: headers mal formados (CRLF) | Como provar/corrigir: confirmar
\r\n\r\nentre headers e corpo. - Sinal: não retorna 413 | Causa provável: limite não aplicado | Como provar/corrigir: reenviar payload > 10 KiB e validar status 413.
TLS
- Sinal: erro de certificado no cliente | Causa provável: autoassinado sem CA confiável | Como provar/corrigir: usar
curl -vkno laboratório e explicar o motivo. - Sinal: handshake falha | Causa provável: arquivo
cert.pem/key.peminválido ou incompatível | Como provar/corrigir: regenerar com OpenSSL e testar de novo. - Sinal: versão TLS abaixo do exigido | Causa provável: ausência de
minimum_version| Como provar/corrigir: fixar TLS1.2+ e validar nocurl -vk/openssl s_client.