Gabriel / Ramos

⟨ pintor de pixel ⟩

  • Blog
  • O que é e como funciona um Proxy?

O que é e como funciona um Proxy?

Conceitos e um pequeno exemplo prático desse personagem importante

7 min. de leitura

Foto por Anatoly Ramonov

#Primeiro, a tradução do termo

Ao jogar o termo em ferramentas como o Google Tradutor você pode ver que Proxy vai ser traduzido pra algo como "procurador" ou "representante" e isso pode ser um bom começo de conversa.

É comum vermos em cidades serviços de despachante, em contabilidades e até em assuntos relacionados a multas veiculares. Um despachante, caso você tenha usado um, nada mais é do que uma pessoa responsável por realizar uma série de envios e documentações burocráticas, por outra pessoa, atuando como um representante.

O mesmo acontece quando você precisa fazer uma procuração, que é um documento onde você autoriza e "concede permissões ou poderes" a outra pessoa, de forma que ela possa tomar alguma decisão (ou assinar algum documento importante) como se fosse você.

Em resumo, um proxy é uma camada que vai atuar entre um cliente e um servidor, sendo responsável por apenas repassar e intermediar alguma operação, como acesso a um site, por exemplo.

#E como um Proxy funciona?

Camadas de Proxy (também chamados de "forward-proxy" ou proxy de encaminhamento, diferente do "reverse-proxy", ou proxy reverso, que veremos adiante) servem para aplicar alguma lógica nos acessos em sua rede antes de encaminhar esse acesso ao destino.

Você já deve ter ouvido que empresa X ou Y "barra o acesso a algum site", por exemplo. Isso é um ótimo exemplo de um Proxy sendo aplicado na prática.a

Dentro de sua rede local, você pode configurar seu Proxy de maneira a proibir acesso a um ou vários domínios específicos, caso queira.

Dessa maneira, esse Proxy foi configurado e mantido por você, dentro da sua rede, como cliente e não chega a enviar acessos aos servidores do site que você bloqueou.

A sequência de acesso seria algo mais ou menos assim:

clientes -> proxy -> internet -> servidores

E toda essa configuração viveria "antes" de atingir qualquer conexão externa com a internet e principalmente, antes de atingir os servidores do site que você está tentando acessar.

#E o que é um Proxy reverso?

Parecido com um Proxy comum, mas geralmente é transparente pra qualquer usuário em um sistema. Geralmente quem disponibiliza e cuida do Proxy reverso está do lado de quem cuida de um serviço ou website que você acessa.

Geralmente Proxies reversos podem ter algumas utilidades como balanceamento de carga, atuar como uma cache e outras coisas.

Não é responsabilidade sua, como usuário, saber quantos servidores um website possui, você apenas quer acessar uma URL e pronto, consumir seu conteúdo, certo?

No entanto, o time responsável por desenvolver o website que você está tentando acessar, pode estar usando um Proxy reverso pra otimizar a performance de entrega e aplicar alguma lógica interna aos servidores que fornecem conteúdo que você está tentando acessar.

Como nesse caso, o Proxy foi disponibilizado pelo time que está desenvolvendo o serviço que você quer acessar, a sequência de acesso seria algo mais ou menos assim:

clientes -> internet -> proxy -> servidores

#Um pequeno exemplo

Para ficar um pouco mais claro, vamos realizar um pequeno exemplo prático.

Em resumo, vimos até aqui que um Proxy nada mais é do que um personagem, em uma determinada estrutura de conexão, que faz um papel de representante, encaminhando e gerenciando algum acesso.

Vamos usar o objeto Proxy do JavaScript pra entender um pouco como ele funciona e ver um exemplo de sua implementação. Claro que em nosso exemplo, esse Proxy é apenas um objeto da linguagem de programação, mas a funcionalidade de atuar como um "representante" é exatamente a mesma.

No nosso cenário, teremos um pequeno objeto de usuario com dois tipos de permissões (roles) possíveis, sendo escrita e leitura:

// nosso objeto de usuario
const usuario = {
    nome: 'Gabriel',
    role: 'escrita'
};

Para começar a usar um Proxy em JavaScript é bem simples, basta executar sua função construtora new Proxy() que recebe 2 parâmetros: o objeto alvo inicial (nosso usuario) e um objeto com configurações (chamado também de "handler"). Por hora, esse nosso objeto será vazio:

const usuario = {
    nome: 'Gabriel',
    role: 'escrita'
};

// criamos objeto de configuracoes vazio
const configuracoes = {};

// criamos nosso usuarioComProxy
const usuarioComProxy = new Proxy(usuario, configuracoes);

Já temos nosso usuarioComProxy que será a versão "representante” do objeto usuario com as regras que ainda vamos definir.

Agora nosso exemplo vai começar a ficar interessante. No objeto de configuração que criamos, podemos adicionar alguns utilitários (na documentação são chamados de "traps", como armadilhas) responsáveis por interceptar o acesso e a modificação do nosso objeto.

Dessa forma, podemos criar uma lógica e definir alguns comportamentos a serem executados em momentos como ler e alterar propriedades de um objeto.

Pode parecer confuso, mas vamos começar com um simples exemplo de log: a cada vez que qualquer propriedade de um objeto for acessada, vamos mostrar uma mensagem Acessando propriedade ${propriedade} no console.

Para fazer isso, podemos definir uma função nas nossas configurações que atuará sempre que uma propriedade for acessada e isso é possível através do manipulador get. Esse manipulador recebe dois parâmetros por padrão, o objeto e a chave que estão sendo acessados no momento.

Com isso, basta realizar nosso console.log normalmente:

const usuario = {
    nome: 'Gabriel',
    role: 'escrita'
};

const configuracoes = {
  // criamos nosso manipulador get
	get(objeto, chave) {
		// colocamos nosso console.log
		console.log(`Acessando propriedade ${chave}`);
	}
};

const usuarioComProxy = new Proxy(usuario, configuracoes);
// tentamos acessar uma propriedade existente
console.log(usuarioComProxy.nome); // undefined
// tentamos acessar uma propriedade inexistente
console.log(usuarioComProxy.sobrenome); // undefined

Ao acessar as propriedades nome e sobrenome, você verá que no console as seguintes mensagens aparecerão:

  • Acessando propriedade nome
  • Acessando propriedade sobrenome

No entanto, o valor dessas propriedades não apareceu como deveria: não foi impresso Gabriel no console. Isso acontece pelo fato de nosso manipulador get ter atuado como deveria. Em nossa lógica, não retornamos a propriedade esperada, apenas executamos um console.log. Por sua vez, a função get retorna undefined e é isso que aparece no final do processo.

Vamos ajustar nossa função de forma que ela retorne o valor do objeto na chave escolhida:

const usuario = {
    nome: 'Gabriel',
    role: 'escrita'
};

const configuracoes = {
	get(objeto, chave) {
		console.log(`Acessando propriedade ${chave}`);

		// retornamos o valor de objeto/chave desejada
		return objeto[chave];
	}
};

const usuarioComProxy = new Proxy(usuario, configuracoes);

console.log(usuarioComProxy.nome); // "Gabriel"
console.log(usuarioComProxy.sobrenome); // undefined

Maravilha, agora tudo funciona como deveria. Ao acessar a propriedade nome podemos receber seu valor e propriedades inexistentes, como sobrenome nos retornam undefined, como esperado.

Agora vamos criar uma pequena lógica de validação: comentamos no início do exemplo que somente poderíamos ter 2 possíveis roles para um objeto de usuário, seja do tipo escrita ou leitura, certo?

Vamos criar um array para guardar esses dois valores:

// criamos um array de roles
const roles = ['escrita', 'leitura'];

const usuario = {
    nome: 'Gabriel',
    role: 'escrita'
};

const configuracoes = {
	get(objeto, chave) {
		console.log(`Acessando propriedade ${chave}`);

		return objeto[chave];
	}
};

const usuarioComProxy = new Proxy(usuario, configuracoes);

Agora, utilizando o modificador set podemos criar uma lógica que faça o que precisamos. Essa função, por sua vez, recebe não somente o objeto e a chave sendo modificada, mas também o valor que está sendo atribuído.

const roles = ['escrita', 'leitura'];

const usuario = {
    nome: 'Gabriel',
    role: 'escrita'
};

const configuracoes = {
	get(objeto, chave) {
		console.log(`Acessando propriedade ${chave}`);

		return objeto[chave];
	},
	// criamos o modificador
	set(objeto, chave, valor) {}
};

const usuarioComProxy = new Proxy(usuario, configuracoes);

Agora basta apenas desenvolver nossa lógica, de forma que, ao alterar a chave role verifiquemos se é uma das roles salvas no nosso array. Caso não seja, podemos disparar um erro, por exemplo. Caso contrário, podemos deixar a operação ocorrer normalmente.

const roles = ['escrita', 'leitura'];

const usuario = {
    nome: 'Gabriel',
    role: 'escrita'
};

const configuracoes = {
	get(objeto, chave) {
		console.log(`Acessando propriedade ${chave}`);

		return objeto[chave];
	},
	set(objeto, chave, valor) {
		// mostramos uma mensagem qualquer
		console.log(`Alterando ${chave} para ${valor}`);
		// caso a chave sendo alterada seja "role"
		// e o valor não esteja incluso no array de "roles"
		if (chave === 'role' && !roles.includes(valor)) {
			// disparamos um erro
			throw new Error(`Role com valor ${valor} não é existente no sistema`)
		}
		// caso contrário, alteramos a role normalmente
		objeto[chave] = valor;
	}
};

const usuarioComProxy = new Proxy(usuario, configuracoes);

Com isso podemos fazer dois pequenos testes e notar que nossa lógica funciona: vamos tentar alterar a role do usuarioComProxy para admin (um valor inválido) e depois para leitura (um valor válido):

usuarioComProxy.role = 'admin'; // vai disparar um erro
console.log(usuarioComProxy.role);

Você deve ter recebido um erro como Uncaught Error: Role com valor admin não é existente no sistema. Mas agora, ao tentar alterar para uma role válida, tudo deverá funcionar normalmente.

usuarioComProxy.role = 'leitura';
console.log(usuarioComProxy.role); // leitura

#Resumo e código final

Nosso código, no final, é o seguinte:

const roles = ['escrita', 'leitura'];

const usuario = {
    nome: 'Gabriel',
    role: 'escrita'
};

const configuracoes = {
	get(objeto, chave) {
		console.log(`Acessando propriedade ${chave}`);

		return objeto[chave];
	},
	set(objeto, chave, valor) {
		console.log(`Alterando ${chave} para ${valor}`);

		if (chave === 'role' && !roles.includes(valor)) {
			throw new Error(`Role com valor ${valor} não é existente no sistema`)
		}

		objeto[chave] = valor;
	}
};

const usuarioComProxy = new Proxy(usuario, configuracoes);

console.log(usuarioComProxy.nome); // Gabriel
console.log(usuarioComProxy.sobrenome); // undefined

// dispararia um erro
// usuarioComProxy.role = 'admin';

usuarioComProxy.role = 'leitura';
console.log(usuarioComProxy.role); // leitura

Implementamos um pequeno exemplo de Proxy, com um log toda vez que qualquer chave é acessada do nosso objeto e com uma lógica que proíbe a mudança da "role" que definimos, disparando um erro quando é de um tipo inválido.

Espero que tenha gostado!


Compartilhe