Console Playground
Blog

Protocol Buffers Playground: Compile e Teste Protobufs no Navegador

Trabalhar com Protocol Buffers geralmente significa configurar protoc, plugins específicos de linguagem e pipelines de compilação. E se você pudesse simplesmente carregar um arquivo .proto e imediatamente começar a serializar e desserializar mensagens?

Hoje vamos mostrar como usar o unsandbox como um protobuf playground - perfeito para aprender, depurar e prototipagem rápida.

A Configuração: Zero Instalação Necessária

Os contêineres unsandbox vêm com:

  • protoc 3.21.12 (compilador protobuf)
  • Biblioteca Python protobuf
  • Gem Ruby google-protobuf
  • Suporte protobuf para Go
  • Suporte completo a arquivos de entrada

Isso significa que você pode carregar um schema .proto e imediatamente trabalhar com ele - nenhuma instalação local necessária.

Exemplo 1: Seu Primeiro Protobuf em Python

Vamos definir uma mensagem simples Person e serializá-la:

person.proto:

syntax = "proto3";

message Person {
  string name = 1;
  int32 age = 2;
}

Requisição da API:

{
  "language": "python",
  "code": "import subprocess, sys, os\nos.chdir('/tmp/input')\nsubprocess.run(['protoc', '--python_out=.', 'person.proto'], check=True)\nsys.path.insert(0, '.')\nimport person_pb2\n\n# Create a Person message\nperson = person_pb2.Person(name='Alice', age=30)\nprint(f'Created: {person}')\n\n# Serialize to binary\ndata = person.SerializeToString()\nprint(f'Serialized ({len(data)} bytes): {data.hex()}')\n\n# Deserialize back\nperson2 = person_pb2.Person()\nperson2.ParseFromString(data)\nprint(f'Deserialized: name={person2.name}, age={person2.age}')",
  "input_files": [
    {"filename": "person.proto", "content": "c3ludGF4ID0gInByb3RvMyI7CgptZXNzYWdlIFBlcnNvbiB7CiAgc3RyaW5nIG5hbWUgPSAxOwogIGludDMyIGFnZSA9IDI7Cn0K"}
  ]
}

Saída:

Created: name: "Alice"
age: 30

Serialized (9 bytes): 0a05416c696365101e
Deserialized: name=Alice, age=30

O que aconteceu:

  1. Carregou person.proto como base64
  2. protoc compilou para person_pb2.py
  3. Criou uma mensagem Person
  4. Serializou para 9 bytes: 0a05416c696365101e
  5. Desserializou de volta

Exemplo 2: Decodificar Binário Protobuf Desconhecido

Tem um arquivo .bin misterioso e seu schema? Carregue ambos e decodifique:

{
  "language": "python",
  "code": "import subprocess, sys, os\nos.chdir('/tmp/input')\nsubprocess.run(['protoc', '--python_out=.', 'person.proto'], check=True)\nsys.path.insert(0, '.')\nimport person_pb2\n\n# Read the binary protobuf data\nwith open('data.bin', 'rb') as f:\n    data = f.read()\nprint(f'Binary data: {data.hex()}')\n\n# Decode it\nperson = person_pb2.Person()\nperson.ParseFromString(data)\nprint(f'Decoded: name={person.name}, age={person.age}')",
  "input_files": [
    {"filename": "person.proto", "content": "c3ludGF4ID0gInByb3RvMyI7CgptZXNzYWdlIFBlcnNvbiB7CiAgc3RyaW5nIG5hbWUgPSAxOwogIGludDMyIGFnZSA9IDI7Cn0K"},
    {"filename": "data.bin", "content": "CgVBbGljZRAe"}
  ]
}

Saída:

Binary data: 0a05416c696365101e
Decoded: name=Alice, age=30

Isso é perfeito para depuração - cole seu dump hexadecimal, converta para base64 e veja o que está dentro!

Exemplo 3: Mensagens Aninhadas e Campos Repetidos

Vamos tentar algo mais complexo:

addressbook.proto:

syntax = "proto3";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

Código Python para popular e serializar:

import subprocess, sys, os
os.chdir('/tmp/input')
subprocess.run(['protoc', '--python_out=.', 'addressbook.proto'], check=True)
sys.path.insert(0, '.')
import addressbook_pb2

# Create an address book
book = addressbook_pb2.AddressBook()

# Add first person
alice = book.people.add()
alice.name = "Alice"
alice.id = 1234
alice.email = "alice@example.com"
phone = alice.phones.add()
phone.number = "555-1234"
phone.type = addressbook_pb2.Person.MOBILE

# Add second person
bob = book.people.add()
bob.name = "Bob"
bob.id = 5678
bob.email = "bob@example.com"
phone = bob.phones.add()
phone.number = "555-5678"
phone.type = addressbook_pb2.Person.WORK

# Serialize
data = book.SerializeToString()
print(f"Address book ({len(data)} bytes):")
print(f"Hex: {data.hex()}")

# Show the text format
print("\nText format:")
print(book)

Saída:

Address book (62 bytes):
Hex: 0a1e0a05416c696365...

Text format:
people {
  name: "Alice"
  id: 1234
  email: "alice@example.com"
  phones {
    number: "555-1234"
  }
}
people {
  name: "Bob"
  id: 5678
  email: "bob@example.com"
  phones {
    number: "555-5678"
    type: WORK
  }
}

Exemplo 4: Inspeção do Formato Wire Bruto

Quer entender o formato wire do protobuf? Vamos decodificá-lo byte por byte:

import subprocess, sys, os
os.chdir('/tmp/input')
subprocess.run(['protoc', '--python_out=.', 'person.proto'], check=True)
sys.path.insert(0, '.')
import person_pb2

person = person_pb2.Person(name="Alice", age=30)
data = person.SerializeToString()

print("Wire format breakdown:")
print(f"Raw bytes: {data.hex()}")
print()

# Decode wire format manually
i = 0
while i < len(data):
    tag_byte = data[i]
    field_number = tag_byte >> 3
    wire_type = tag_byte & 0x07

    wire_type_names = {0: 'varint', 1: '64-bit', 2: 'length-delimited', 5: '32-bit'}
    print(f"Field {field_number}, Wire type {wire_type} ({wire_type_names.get(wire_type, 'unknown')})")

    i += 1
    if wire_type == 0:  # Varint
        value = 0
        shift = 0
        while data[i] & 0x80:
            value |= (data[i] & 0x7f) << shift
            shift += 7
            i += 1
        value |= data[i] << shift
        i += 1
        print(f"  Value: {value}")
    elif wire_type == 2:  # Length-delimited
        length = data[i]
        i += 1
        content = data[i:i+length]
        i += length
        print(f"  Length: {length}, Content: {content} ({content.hex()})")

Saída:

Wire format breakdown:
Raw bytes: 0a05416c696365101e

Field 1, Wire type 2 (length-delimited)
  Length: 5, Content: b'Alice' (416c696365)
Field 2, Wire type 0 (varint)
  Value: 30

Agora você pode ver exatamente como o protobuf codifica dados!

Exemplo 5: Teste de Evolução de Schema

O Protobuf é famoso pela compatibilidade regressiva/progressiva. Vamos testar:

v1.proto (original):

syntax = "proto3";
package v1;
message User {
  string name = 1;
  int32 age = 2;
}

v2.proto (novo campo adicionado):

syntax = "proto3";
package v2;
message User {
  string name = 1;
  int32 age = 2;
  string email = 3;  // New field!
}

Teste de compatibilidade progressiva:

import subprocess, sys, os
os.chdir('/tmp/input')

# Compile both versions
subprocess.run(['protoc', '--python_out=.', 'v1.proto'], check=True)
subprocess.run(['protoc', '--python_out=.', 'v2.proto'], check=True)

sys.path.insert(0, '.')
import v1_pb2
import v2_pb2

# Create v2 message with new field
user_v2 = v2_pb2.User(name="Alice", age=30, email="alice@example.com")
data = user_v2.SerializeToString()
print(f"v2 serialized: {data.hex()}")

# Read with v1 (old code reading new data)
user_v1 = v1_pb2.User()
user_v1.ParseFromString(data)
print(f"v1 reads: name={user_v1.name}, age={user_v1.age}")
print("Email field? v1 ignores unknown fields - forward compatible!")

Referência Rápida: Codificar Seus Arquivos Proto em Base64

Para fazer upload de arquivos, você precisa de base64. Veja como:

Linux/Mac:

base64 -w 0 person.proto

Python:

import base64
with open('person.proto', 'rb') as f:
    print(base64.b64encode(f.read()).decode())

JavaScript:

const content = "syntax = \"proto3\";\n\nmessage Person {\n  string name = 1;\n  int32 age = 2;\n}\n";
console.log(btoa(content));

Casos de Uso

1. Aprendendo Protobufs

Pule a complicação da instalação. Apenas escreva um .proto, faça upload e experimente.

2. Depurando Formato Wire

Tem um dump hexadecimal de uma captura de rede? Cole, decodifique, entenda.

3. Validação de Schema

Teste se seu schema compila antes de fazer commit. Verifique se os números dos campos não entram em conflito.

4. Teste Entre Linguagens

Serialize em Python, desserialize em Go. Verifique se suas mensagens funcionam entre linguagens.

5. Documentação de API

Mostre exemplos funcionais de suas mensagens protobuf com saída serializada real.

Limites

  • Tamanho do arquivo: Máximo de 5MB por arquivo (suficiente para schemas)
  • Tempo de execução: Timeout de 30 segundos
  • Versão do protoc: 3.21.12 (proto3 totalmente suportado)

Experimente Agora

  1. Vá para unsandbox.com
  2. Selecione Python
  3. Cole seu código protobuf
  4. Faça upload do seu arquivo .proto (ou use o parâmetro base64 input_files)
  5. Execute!

Sem instalação. Sem pipeline de compilação. Apenas protobufs.

Feliz serialização!