O projeto GOPEN foi criado no intuito de ajudar os desenvolvedores a terem uma API Gateway robusta e de fácil manuseio, com a oportunidade de atuar em melhorias agregando a comunidade, e o mais importante, sem gastar nada. Foi desenvolvida, pois muitas APIs Gateway do mercado de forma gratuita, não atendem muitas necessidades mínimas para uma aplicação, induzindo-o a fazer o upgrade.
Com essa nova API Gateway você não precisará equilibrar pratos para economizar na sua infraestrutura e arquitetura, e ainda otimizará o seu desenvolvimento, veja abaixo todos os recursos disponíveis:
-
Json de configuração simplificado para múltiplos ambientes.
-
Configuração rápida de variáveis de ambiente.
-
Execução via docker com hot reload de configuração opcional.
-
Timeout granular, com uma configuração padrão para todos os endpoints, mas podendo especificar para cada endpoint.
-
Cache granular, com estratégia e condição para o armazenamento customizável.
-
Armazenamento de cache local ou global utilizando Redis.
-
Limitador de uso e de carga granular, com uma configuração padrão para todos os endpoints, mas podendo especificar para cada endpoint.
-
Segurança de CORS com validações de origens, método HTTP e headers.
-
Timeout linear, enviando o tempo restante para processamento num cabeçalho de requisição.
-
Múltiplos middlewares, para serem usados posteriormente no endpoint caso necessário.
-
Filtragem personalizada de envio de Headers, Query e Body para os backends do endpoint.
-
Processamento de múltiplos backends por endpoint.
-
Aborte o processo de execução dos backends pelo código de status de forma personalizada.
-
Chamadas concorrentes ao backend caso configurado.
-
Customize sua requisição e resposta do backend utilizando nossos recursos:
- Omita informações.
- Mapeamento. (Header, Query e Body)
- Projeção. (Header, Query e Body)
- Personalização da nomenclatura do body.
- Personalização do tipo do conteúdo do body.
- Comprima o body de requisição usando GZIP ou DEFLATE.
- Modificadores, são pontos e ações especificas para modificar algum conteúdo de requisição ou resposta.
- Agrupe o body de resposta em um campo específico informado.
-
Customize sua resposta de endpoint utilizando nossos recursos:
- Omita informações vazias do body.
- Agregue múltiplas respostas dos backends.
- Personalização do tipo do body.
- Personalização da nomenclatura do body.
- Comprima o body de requisição usando GZIP ou DEFLATE.
-
Rastreamento distribuído utilizando Elastic APM, Dashboard personalizado no Kibana, e logs bem estruturados com informações relevantes de configuração e acessos à API.
Para entender como funciona, precisamos explicar primeiro a estrutura dos ambientes dinâmicos que GOPEN aceita para sua configuração e variáveis de ambiente, então vamos lá!
Independente de como irá utilizar a API Gateway, ela exige duas variáveis de ambiente que são:
Porta aonde sua API Gateway irá ouvir e servir.
Exemplo: 8080
Qual ambiente sua API Gateway irá atuar.
Exemplo: dev
Na estrutura do projeto, em sua raiz precisará ter uma pasta chamada "gopen" e dentro dela precisa ter as pastas contendo os nomes dos seus ambientes, você pode dar o nome que quiser, essa pasta precisará ter pelo menos o arquivo ".json" de configuração da API Gateway, ficará mais o menos assim, por exemplo:
gopen-gateway
| - cmd
| - internal
| - gopen
| - dev
| - .json
| - .env
| - prd
| - .json
| - .env
nome-do-seu-projeto
| - docker-compose.yml
| - gopen
| - dev
| - .json
| - .env
| - prd
| - .json
| - .env
Com base nesse arquivo JSON de configuração obtido através da variável de ambiente GOPEN_ENV informada, a aplicação terá seus endpoints e suas regras definidas, veja abaixo todos os campos possíveis e seus conceitos e regras:
- $schema
- @comment
- version
- hot-reload
- store
- timeout
- cache
- limiter
- security-cors
- middlewares
- endpoints
- @comment
- path
- method
- timeout
- cache
- limiter
- abort-if-status-codes
- response
- beforewares
- afterwares
- backends
- @comment
- hosts
- path
- method
- request
- response
Campo opcional, para o auxílio na escrita e regras do próprio JSON de configuração, podendo ser ultima versão:
https://raw.githubusercontent.com/tech4works/gopen-gateway/main/json-schema.json
Ou uma versão específica:
https://raw.githubusercontent.com/tech4works/gopen-gateway/v1.0.0/json-schema.json
Campo opcional, do tipo string, livre para anotações.
Campo opcional, do tipo string, usado para controle de versão e também usado no retorno do endpoint estático /version.
Campo opcional, do tipo booleano, o valor padrão é false
, é utilizado para o carregamento automático quando
houver alguma alteração no arquivo .json e .env na pasta do ambiente selecionado.
Campo opcional, do tipo objeto, o valor padrão é o armazenamento local em cache, caso seja informado, o campo redis
passa a ser obrigatório e o campo address
também.
⚠️ IMPORTANTECaso utilize o armazenamento global de cache, o Redis, é indicado que os valores de endereço e senha sejam preenchidos utilizando variável de ambiente, como no exemplo acima.
Campo opcional, do tipo string, o valor padrão é 30s
, esse campo é responsável pelo tempo máximo de duração do
processamento de cada requisição.
Caso a requisição ultrapasse esse tempo informado, á API Gateway irá abortar todas as transações em andamento e
retornará o código de status 504 (Gateway Timeout)
.
Veja mais sobre esse retorno clicando aqui.
Valores aceitos
- s para segundos
- m para minutos
- h para horas
- ms para milissegundos
- us (ou µs) para microssegundos
- ns para nanossegundos
Exemplos
- 10s
- 5ms
- 1h30m
- 1.5h
⚠️ IMPORTANTECaso seja informado no endpoint.timeout, damos prioridade ao valor informado do endpoint, caso contrário, seguiremos com o valor informado ou padrão desse campo, na raiz do JSON de configuração.
Campo opcional, é responsável pela configuração global de cache, caso o endpoint em específico informe o campo
endpoint.cache.enabled com o valor true
apenas, ele irá herdar os outros valores do mesmo
para sí.
O valor do cache é apenas gravado 1 vez a cada X duração informada no campo duration
.
Os campos only-if-status-codes e only-if-methods são utilizados para verificar se naquele endpoint habilitado a ter cache, pode ser lido e escrito o cache com base no método HTTP e código de status de resposta, veja mais sobre eles abaixo.
Caso a resposta não seja "fresca", ou seja, foi respondida pelo cache, o header X-Gopen-Cache
terá o valor true
caso contrário, o valor será false
.
⚠️ IMPORTANTECaso o objeto seja informado na estrutura do endpoint.cache, damos prioridade aos valores informados lá, caso contrário, seguiremos com os valores informados nesse campo.
Campo obrigatório, do tipo string, indica o tempo que o cache irá durar.
Valores aceitos:
- s para segundos
- m para minutos
- h para horas
- ms para milissegundos
- us (ou µs) para microssegundos
- ns para nanossegundos
Exemplos
- 1h
- 15.5ms
- 1h30m
- 1.5m
Campo opcional, do tipo lista de string, é utilizado para definir a estratégia da chave do cache a partir dos headers informados.
O valor padrão de chave de cache é pela url e método HTTP da requisição tornando-o um cache global
por endpoint, caso informado os cabeçalhos a serem usados na estrátegia eles são agregados nos valores padrões de
chave, por exemplo, vamos utilizar o campo X-Forwarded-For
e o Device
do cabeçalho, o valor final da chave
ficaria:
GET:/users/find/479976139:177.130.228.66:95D4AF55-733D-46D7-86B9-7EF7D6634EBC
A descrição da lógica por trás dessa chave é:
método:url:X-Forwarded-For:Device
Sem a estrátegia preenchida, a lógica padrão fica assim:
método:url
Então o valor padrão para esse endpoint fica assim sem a estrátegia preenchida:
GET:/users/find/479976139
Nesse exemplo tornamos o cache antes global para o endpoint em espécifico, passa a ser por cliente! Lembrando que isso é um exemplo simples, você pode ter a estrátegia que quiser com base no header de sua aplicação.
Campo opcional, do tipo lista de string, é responsável por decidir se irá ler e gravar o cache do endpoint (que está habilitado a ter cache) pelo método HTTP informado.
O valor padrão é apenas o método HTTP GET
, caso informada vazia, qualquer método HTTP será aceito.
Campo opcional, do tipo lista de inteiro, é responsável por decidir se irá gravar o cache do endpoint (que está habilitado a ter cache) pelo código de status HTTP de resposta do mesmo.
O valor padrão é uma lista de códigos de status HTTP de sucessos reconhecidos, caso informada vazia, qualquer código de status HTTP de resposta será aceito.
Campo opcional, do tipo booleano, é responsável por considerar ou não o header de requisição Cache-Control
para tomada
de decisão se irá gravar ou ler o cache.
O valor padrão é false
, caso seja informado como true
a API Gateway irá considerar o header Cache-Control
seguindo
as regras a seguir a partir do valor informado na requisição ou na resposta dos backends:
no-cache
Esse valor é apenas considerado no header da requisição, caso informado desconsideramos a leitura do cache e seguimos com o processo normal para obter a resposta "fresca".
no-store
Esse valor é considerado apenas na resposta escrita por seus backends, caso informado não gravamos o cache.
Campo opcional, do tipo objeto, é responsável pelas regras de limitação da API Gateway, seja de tamanho ou taxa, os valores padrões variam de campo a campo, veja:
Campo opcional, do tipo string, o valor padrão é 1MB
, é responsável por limitar o tamanho do cabeçalho de requisição.
Caso o tamanho do cabeçalho ultrapasse o valor informado, a API Gateway irá abortar a requisição com o código de
status 431 (Request header fields too large)
.
Veja mais sobre esse retorno clicando aqui.
Valores aceitos
- B para Byte
- KB para KiloByte
- MB para Megabyte
- GB para Gigabyte
- TB para Terabyte
- PB para Petabyte
- EB para Exabyte
- ZB para Zettabyte
- YB para Yottabyte
Exemplos
- 1B
- 50KB
- 5MB
- 1.5GB
Campo opcional, do tipo string, o valor padrão é 3MB
, é responsável por limitar o tamanho do corpo da requisição.
Caso o tamanho do corpo ultrapasse o valor informado, a API Gateway irá abortar a requisição com o código de status
413 (Request entity too large)
.
Valores aceitos
- B para Byte
- KB para KiloByte
- MB para Megabyte
- GB para Gigabyte
- TB para Terabyte
- PB para Petabyte
- EB para Exabyte
- ZB para Zettabyte
- YB para Yottabyte
Exemplos
- 1B
- 50KB
- 5MB
- 1.5GB
Campo opcional, do tipo string, o valor padrão é 5MB
, é responsável por limitar o tamanho do corpo multipart/form da
requisição, geralmente utilizado para envio de arquivos, imagens, etc.
Caso o tamanho do corpo ultrapasse o valor informado, a API Gateway irá abortar a requisição com o código de status
413 (Request entity too large)
.
Veja mais sobre esse retorno clicando aqui.
Valores aceitos
- B para Byte
- KB para KiloByte
- MB para Megabyte
- GB para Gigabyte
- TB para Terabyte
- PB para Petabyte
- EB para Exabyte
- ZB para Zettabyte
- YB para Yottabyte
Exemplos
- 1B
- 50KB
- 5MB
- 1.5GB
Campo opcional, do tipo objeto, caso seja informado, o campo capacity torna-se obrigatório, esse objeto é responsável por limitar a taxa de requisição pelo IP.
O limite é imposto obtendo a capacidade máxima pelo campo capacity por X duração, informado no campo every.
Caso essa capacidade seja ultrapassada, a API Gateway por segurança abortará a requisição, retornando
429 (Too many requests)
.
Veja mais sobre esse retorno clicando aqui.
Campo obrigatório, do tipo inteiro, indica a capacidade máxima de requisições.
O valor padrão é 5
, e o mínimo que poderá ser informado é 1
.
Campo opcional, do tipo string, o valor padrão é 1s
, indica o valor da duração da verificação da capacidade máxima de
requisições.
Valores aceitos:
- s para segundos
- m para minutos
- h para horas
- ms para milissegundos
- us (ou µs) para microssegundos
- ns para nanossegundos
Exemplos
- 1h
- 15.5ms
- 1h30m
- 1.5m
Campo opcional, do tipo objeto, usado para segurança do CORS da API Gateway, todos os campos por padrão são vazios, não restringindo os valores de origem, métodos e cabeçalhos.
Caso queira restringir, e a requisição não corresponda com as configurações impostas, a API Gateway por segurança
irá abortar a requisição retornando 403 (Forbidden)
.
Campo opcional, do tipo lista de string, os itens da lista precisam indicar quais IPs de origem a API Gateway permite receber nas requisições.
Campo opcional, do tipo lista de string, os itens da lista precisam indicar quais métodos HTTP a API Gateway permite receber nas requisições.
Campo opcional, do tipo lista de string, os itens da lista precisam indicar quais campos de cabeçalho HTTP a API Gateway permite receber nas requisições.
Campo opcional, é responsável pela configuração de seus middlewares de aplicação, é um mapa com chaves em string mencionando o nome do seu middleware, esse nome poderá ser utilizado em seus endpoints como endpoint.beforewares e endpoint.afterwares.
O valor da chave é um objeto de backend, porém, com uma observação, esse objeto terá sua resposta caso não abortada, omitida automáticamente pelo endpoint, já que respostas de middlewares não são exibidas para o cliente final HTTP, porém, sua resposta será armazenada ao longo da requisição HTTP feita no endpoint, podendo ter seus valores de requisição e resposta obtidos e manipulados.
Para entender melhor essa ferramenta poderosa, na prática, veja os exemplos de middlewares usados como beforewares
e afterwares
feitos no projeto de playground.
Campo obrigatório, é uma lista de objeto, representa cada endpoint da API Gateway que será registrado para ouvir e servir as requisições HTTP.
Campo opcional, do tipo string, campo livre para anotações.
Campo obrigatório, do tipo string, responsável pelo caminho URI do endpoint que irá ouvir e servir.
Caso queira ter parâmetros dinâmicos nesse endpoint, apenas use o padrão ":nome do parâmetro"
por exemplo
"/users/:id/status/:status"
, a API Gateway irá entender que teremos 2 parâmetros dinâmicos desse endpoint,
esses valores podem ser repassados para os backends subjacentes, exemplo:
Endpoint
- path:
"/users/:id/status/:status"
- resultado:
"/users/1/status/removed"
Backend 1
- path:
"/users/:id"
- resultado:
"/users/1"
Backend 2
- path:
"/users/:id/status/:status"
- resultado:
"/users/1/status/removed"
No exemplo acima vemos que o parâmetro pode ser utilizado como quiser como path nas requisições de backend do endpoint em questão.
Campo obrigatório, do tipo string, responsável por definir qual método HTTP o endpoint será registrado.
Campo opcional, do tipo string, é semelhante ao campo timout, porém, será aplicado apenas para o endpoint em questão.
⚠️ IMPORTANTECaso omitido, será herdado o valor do campo timeout.
Campo opcional, do tipo objeto, é responsável pela configuração de cache para o endpoint em questão.
⚠️ IMPORTANTECaso informado, o campo enabled se torna obrigatório, os outros campos, caso omitidos, irá herdar da configuração cache na raiz caso exista e se preenchida.
Se por acaso, tenha omitido o campo
duration
tanto na atual configuração como na configuração cache na raiz, o campo enabled é ignorado considerando-o sempre comofalse
pois não foi informado a duração do cache em ambas configurações.
Campo obrigatório, do tipo booleano, o valor padrão é false
, indica o desejo que tenha cache em seu endpoint ou não.
⚠️ IMPORTANTECaso esteja
true
mas não informado o campoduration
na configuração atual e nem na raiz, esse campo será ignorado considerando-o sempre comofalse
.
Campo opcional, do tipo booleano, caso não informado o valor padrão é false
.
Caso o valor seja true
a API Gateway irá ignorar os parâmetros de busca da URL ao
criar a chave de armazenamento, caso contrário ela considerára os parâmetros de busca da URL
ordenando alfabéticamente as chaves e valores.
É semelhante ao campo cache.duration, porém, será aplicado apenas para o endpoint em questão.
⚠️ IMPORTANTECaso omitido, será herdado o valor do campo cache.duration.
Caso seja omitido nas duas configurações, o campo enabled será ignorado considerando-o sempre como
false
.
Campo opcional, do tipo lista de string, é semelhante ao campo cache.strategy-headers, porém, será aplicado apenas para o endpoint em questão.
⚠️ IMPORTANTECaso omitido, será herdado o valor do campo cache.strategy-headers.
Caso seja informado vazio, o valor do não será herdado, porém, será aplicado o valor padrão para o endpoint em questão.
Campo opcional, do tipo lista de inteiro, é semelhante ao campo cache.only-if-status-codes, porém, será aplicado apenas para o endpoint em questão.
⚠️ IMPORTANTECaso omitido, será herdado o valor do campo cache.only-if-status-codes.
Caso seja informado vazio, o valor do não será herdado, porém, será aplicado o valor padrão para o endpoint em questão.
Campo opcional, do tipo booleano, é semelhante ao campo cache.allow-cache-control, porém, será aplicado apenas para o endpoint em questão.
⚠️ IMPORTANTECaso omitido, será herdado o valor do campo cache.allow-cache-control.
Campo opcional, do tipo objeto, é semelhante ao campo limiter, porém, será aplicado apenas para o endpoint em questão.
⚠️ IMPORTANTECaso omitido, será herdado o valor do campo limiter.
Campo opcional, do tipo lista de inteiros, o valor padrão é nulo, indicando que qualquer backend executado no endpoint
que tenha respondido o código de status HTTP maior ou igual a 400 (Bad request)
será abortado.
Caso informado, e um backend retorna o código de status HTTP indicado na configuração, o endpoint será abortado, isso significa que os outros backends configurados após o mesmo, não serão executados, e o endpoint irá retornar a resposta do mesmo ao cliente final.
Caso queira que nenhum código de status HTTP seja abortado no endpoint, apenas informe o campo vazio.
Veja como o endpoint será respondido após um backend ser abortado clicando aqui.
Campo opcional, do tipo objeto, é responsável pela customização da resposta do endpoint.
Veja mais sobre as regras de resposta da API Gateway clicando aqui.
Campo opcional, do tipo string, livre para anotações.
Campo opcional, do tipo booleano, o valor padrão é false
, é responsável por agregar todos os corpos das respostas
recebidas pelos backends em apenas um corpo.
Campo opcional, do tipo string, é responsável por informar qual conteúdo deseja para o corpo de resposta do endpoint.
Valores aceitos
- JSON
- XML
- TEXT
Campo opcional, do tipo string, é responsável por informar qual codificação deseja para o corpo da resposta do endpoint.
Valores aceitos
- NONE (Remove a codificação caso tenha, e retorna sem nenhum tipo de codificação)
- GZIP
- DEFLATE
Campo opcional, do tipo string, é responsável por informar qual nomenclatura deseja para os campos do corpo JSON da resposta do endpoint.
Valores aceitos
- LOWER_CAMEL
- CAMEL
- SNAKE
- SCREAMING_SNAKE
- KEBAB
- SCREAMING_KEBAB
Campo opcional, do tipo booleano, o valor padrão é false
, indica o desejo de omitir os campos vazios do corpo JSON
da resposta do endpoint.
Campo opcional, do tipo lista de string, o valor padrão é vazio, indicando que o endpoint não tem nenhum middleware de pré-requisições.
Caso informado, o endpoint irá executar as requisições, posição por posição, começando no início da lista. Caso o valor em string da posição a ser executada estiver configurada no campo middlewares corretamente, será executado o backend configurado neste campo, caso contrário, irá ignorar a posição apenas imprimindo um log de atenção.
Campo opcional, do tipo lista de string, o valor padrão é vazio, indicando que o endpoint não tem nenhum middleware de pós-requisições.
Caso informado, o endpoint irá executar as requisições, posição por posição, começando no início da lista. Caso o valor em string da posição a ser executada estiver configurada no campo middlewares corretamente, será executado o backend configurado no mesmo, caso contrário, irá ignorar a posição apenas imprimindo um log de atenção.
Campo obrigatório, do tipo lista de objeto, responsável pela execução principal do endpoint é uma lista que indica todos os serviços necessários para que o endpoint execute e retorne a resposta esperada.
Campo opcional, do tipo string, campo livre para anotações.
Campo obrigatório, do tipo lista de string, é responsável pelos hosts do seu serviço que a API Gateway irá chamar juntamente com o campo backend.path.
De certa forma podemos ter um load balancer "burro", pois o backend irá sortear nessa lista qual host irá ser chamado, com isso podemos informar múltiplas vezes o mesmo host para balancear as chamadas, veja:
50% para cada host
[
"https://instance-01",
"https://instance-02"
]
75% no host "instance-01" e 15% no host "instance-02"
[
"https://instance-01",
"https://instance-02",
"https://instance-02",
"https://instance-02"
]
33.3% no host "instance-01" e 66.7% no host "instance-02"
[
"https://instance-01",
"https://instance-02",
"https://instance-02"
]
Campo obrigatório, do tipo string, o valor indica a URL do caminho do serviço backend.
Utilizamos um dos endpoint.backend.hosts informados e juntamos com o path fornecido, por exemplo, no campo hosts temos o valor
[
"https://instance-01",
"https://instance-02"
]
E nesse campo path temos o valor
/users/status
O backend irá construir a seguinte URL de requisição depois do load balance
https://instance-02/users/status
Veja como o host é balanceado clicando aqui.
Campo obrigatório, do tipo string, o valor indica qual método HTTP o serviço backend espera.
Campo opcional, do tipo objeto, é responsável pela customização da requisição que será feita ao backend.
Campo opcional, do tipo string, campo livre para anotações.
Campo opcional, do tipo inteiro, é responsável pela quantidade de requisições concorrentes que deseja fazer ao serviço backend.
O valor padrão é 0
, indicando que será executado apenas 1 requisição de forma síncrona, os valores aceitos são
de no mínimo 2
e no máximo 10
.
Campo opcional, do tipo booleano, o valor padrão é false
, indica o desejo de omitir o cabeçalho da requisição.
Campo opcional, do tipo booleano, o valor padrão é false
, indica o desejo de omitir todos os parâmetros de busca da
requisição.
Campo opcional, do tipo booleano, o valor padrão é false
, indica o desejo de omitir o corpo da requisição.
Campo opcional, do tipo string, é responsável por informar qual conteúdo deseja para o corpo da requisição.
Valores aceitos
- JSON
- XML
- TEXT
Exemplo
Corpo de requisição atual:
{
"id": "Summer",
"name": "Veranito fresquito"
}
Configuramos o campo content-type para XML
e o resultado foi:
<root>
<id>Summer</id>
<name>Veranito fresquito</name>
</root>
Campo opcional, do tipo string, é responsável por informar qual codificação deseja para o corpo da requisição.
Valores aceitos
- NONE (Remove a codificação caso tenha, e retorna sem nenhum tipo de codificação)
- GZIP
- DEFLATE
Campo opcional, do tipo string, é responsável por informar qual nomenclatura deseja para os campos do corpo JSON da requisição.
Valores aceitos
- LOWER_CAMEL
- CAMEL
- SNAKE
- SCREAMING_SNAKE
- KEBAB
- SCREAMING_KEBAB
Exemplo
Corpo atual:
{
"id": "Summer",
"name": "Veranito fresquito",
"start_date": "2017/02/10",
"end_date": "2017/02/15",
"address": {
"street_address": "21 2nd Street",
"city": "New York",
"state": "NY",
"postal_code": "10021"
}
}
Configuramos o campo nomenclature para LOWER_CAMEL
e o resultado foi:
{
"id": "Summer",
"name": "Veranito fresquito",
"startDate": "2017/02/10",
"endDate": "2017/02/15",
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
}
}
Campo opcional, do tipo booleano, o valor padrão é false
, indica o desejo de omitir os campos vazios do corpo JSON
da requisição.
Campo opcional, é responsável por mapear os campos do cabeçalho de requisição, fazendo um de/para do nome do campo atual para o nome desejado, veja o exemplo:
Cabeçalho atual:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-Device-Id: asajlaks212
X-Test-Id: asdkmalsd123
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
Configuração do header-mapper:
{
"X-Value-Id": "X-New-Value-Id",
"X-Device-Id": "X-New-Device-Id",
"X-Test-Id": "X-New-Test-Id"
}
Resultado
Content-Type: application/json
X-New-Value-Id: 4ae6c92d16089e521626
X-New-Device-Id: asajlaks212
X-New-Test-Id: asdkmalsd123
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
⚠️ IMPORTANTESó é permitido a customização de chaves não obrigatórias que não sejam:
- Content-Type
- Content-Encoding
- Content-Length
- X-Forwarded-For
Campo opcional, do mapa chave-valor string, é responsável por mapear os campos dos parâmetros de busca da requisição, fazendo um de/para do nome do campo atual para o nome desejado, veja o exemplo:
URL com os parâmetros de busca:
/users?id=23&email=gabrielcataldo@gmail.com&phone=47991271234
Configuração query-mapper:
{
"id": "user_id",
"email": "mail",
"phone": "phone_number"
}
Resultado:
/users?user_id=23&mail=gabrielcataldo@gmail.com&phone_number=47991271234
Campo opcional, do tipo mapa chave-valor string, é responsável por mapear os campos do corpo JSON da requisição, fazendo um de/para do nome do campo atual para o nome desejado, veja o exemplo:
Corpo atual da requisição:
{
"id": 1,
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
}
}
Configuração do body-mapper:
{
"firstName": "personalData.firstName",
"lastName": "personalData.lastName",
"age": "personalData.age"
}
Resultado:
{
"id": 1,
"personalData": {
"firstName": "John",
"lastName": "Smith",
"age": 25
},
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
}
}
Campo opcional, do tipo objeto, é responsável por customizar o envio de campos do cabeçalho de requisição ao serviço backend.
Valores aceitos para os campos
-1
: Significa que você deseja remover o campo indicado.1
: Significa que você deseja manter o campo indicado.
Exemplo
Cabeçalho atual:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-Device-Id: asajlaks212
X-Test-Id: asdkmalsd
X-User-Id: 123
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
Configuração do header-projection:
{
"X-Value-Id": 1,
"X-User-Id": 1
}
Resultado:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-User-Id: 123
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
⚠️ IMPORTANTESó é permitido a customização de chaves não obrigatórias que não sejam:
- Content-Type
- Content-Encoding
- Content-Length
- X-Forwarded-For
Campo opcional, do tipo objeto, é responsável por customizar o envio de dos parâmetros de busca da requisição ao serviço backend.
Valores aceitos para os campos
-1
: Significa que você deseja remover o campo indicado.1
: Significa que você deseja manter o campo indicado.
Exemplo
URL:
/users?id=23&email=gabrielcataldo@gmail.com&phone=47991271234
Configuração do query-projection:
{
"id": -1
}
Resultado:
/users?email=gabrielcataldo@gmail.com&phone=47991271234
Campo opcional, do tipo objeto, é responsável por customizar o envio de dos campos do corpo JSON da requisição ao serviço backend.
Valores aceitos para os campos
-1
: Significa que você deseja remover o campo indicado.1
: Significa que você deseja manter o campo indicado.
Exemplo
Corpo atual:
{
"id": 1,
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
}
}
Configuração do body-projection:
{
"age": -1,
"address.postalCode": -1
}
Resultado:
{
"id": 1,
"firstName": "John",
"lastName": "Smith",
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY"
}
}
Campo opcional, do tipo lista de objeto, valor padrão é vazio, é responsável por modificações especificas do cabeçalho da requisição ao serviço backend.
Campo opcional, do tipo string, campo livre para anotações.
Campo obrigatório, do tipo string, responsável pela ação a ser tomada na modificação do cabeçalho.
Valores aceitos
-
ADD
: Adiciona a chave informada no campo header.key caso não exista, e agrega o valor informado no campo header.value. -
APD
: Acrescenta o valor informado no campo header.value caso a chave informada no campo header.key exista. -
SET
: Define o valor da chave informada no campo header.key pelo valor passado no campo header.value. -
RPL
: Substitui o valor da chave informada no campo header.key pelo valor passado no campo header.value caso exista. -
DEL
: Remove a chave informada no campo header.key caso exista.
Campo obrigatório, do tipo string, utilizado para indicar qual chave do cabeçalho deve ser modificada.
Campo obrigatório, do tipo string, utilizado como valor a ser usado para modificar a chave indicada no campo header.key.
Temos possibilidades de utilização de valores dinâmicos, e de variáveis de ambiente para esse campo.
⚠️ IMPORTANTESe torna opcional apenas se header.action tiver o valor
DEL
.
Campo opcional, do tipo booleano, o valor padrão é false
indicando que o modificador não deve propagar essa mudança
em questão para os backends seguintes.
Caso informado como true
essa modificação será propagada para os seguintes backends.
Campo opcional, do tipo lista de objeto, valor padrão é vazio, é responsável por modificações especificas dos parâmetros da URL da requisição ao serviço backend.
Campo opcional, do tipo string, campo livre para anotações.
Campo obrigatório, do tipo string, responsável pela ação a ser tomada na modificação dos parâmetros da URL da requisição.
Valores aceitos
-
SET
: Define o valor da chave informada no campo param.key pelo valor passado no campo param.value. -
RPL
: Substitui o valor da chave informada no campo param.key pelo valor passado no campo param.value caso exista. -
DEL
: Remove a chave informada no campo param.key caso exista.
Campo obrigatório, do tipo string, utilizado para indicar qual chave de parâmetro da URL deve ser modificada.
Campo obrigatório, do tipo string, utilizado como valor a ser usado para modificar a chave indicada no campo param.key.
Temos possibilidades de utilização de valores dinâmicos, e de variáveis de ambiente para esse campo.
⚠️ IMPORTANTESe torna opcional apenas se param.action tiver o valor
DEL
.
Campo opcional, do tipo booleano, o valor padrão é false
indicando que o modificador não deve propagar essa mudança
em questão para os backends seguintes.
Caso informado como true
essa modificação será propagada para os seguintes backends.
Campo opcional, do tipo lista de objeto, valor padrão é vazio, responsável pelas modificações de parâmetros de busca da requisição ao serviço backend.
Campo opcional, do tipo string, campo livre para anotações.
Campo obrigatório, do tipo string, responsável pela ação a ser tomada na modificação dos parâmetros de busca da requisição.
Valores aceitos
-
ADD
: Adiciona a chave informada no campo query.key caso não exista, e agrega o valor informado no campo query.value. -
APD
: Acrescenta o valor informado no campo query.value caso a chave informada no campo query.key exista. -
SET
: Define o valor da chave informada no campo query.key pelo valor passado no campo query.value. -
RPL
: Substitui o valor da chave informada no campo query.key pelo valor passado no campo query.value caso exista. -
DEL
: Remove a chave informada no campo query.key caso exista.
Campo obrigatório, do tipo string, utilizado para indicar qual chave de parâmetro de busca deve ser modificada.
Campo obrigatório, do tipo string, utilizado como valor a ser usado para modificar a chave indicada no campo query.key.
Temos possibilidades de utilização de valores dinâmicos, e de variáveis de ambiente para esse campo.
⚠️ IMPORTANTESe torna opcional apenas se query.action tiver o valor
DEL
.
Campo opcional, do tipo booleano, o valor padrão é false
indicando que o modificador não deve propagar essa mudança
em questão para os backends seguintes.
Caso informado como true
essa modificação será propagada para os seguintes backends.
Campo opcional, do tipo lista de objeto, valor padrão é vazio, responsável pelas modificações do corpo da requisição ao serviço backend.
Campo opcional, do tipo string, campo livre para anotações.
Campo obrigatório, do tipo string, responsável pela ação a ser tomada na modificação do corpo da requisição.
Valores aceitos se o corpo for do tipo JSON
-
ADD
: Adiciona a chave informada no campo body.key caso não exista, e agrega o valor informado no campo body.value. -
APD
: Acrescenta o valor informado no campo body.value caso a chave informada no campo body.key exista. -
SET
: Defini o valor da chave informada no campo body.key pelo valor passado no campo body.value. -
RPL
: Substitui o valor da chave informada no campo body.key pelo valor passado no campo body.value caso exista. -
REN
: Renomeia a chave informada no campo body.key pelo valor passado no campo body.value caso exista. -
DEL
: Remove a chave informada no campo body.key caso exista.
Valores aceitos se o corpo for TEXTO
-
ADD
: Agrega o valor informado no campo body.value ao texto. -
APD
: Acrescenta o valor informado no campo body.value caso body não for vazio. -
RPL
: Irá substituir todos os valores semelhantes à chave informada no campo body.key pelo valor passado no campo body.value. -
DEL
: Remove todos os valores semelhantes à chave informada no campo body.key.
Campo obrigatório, do tipo string, utilizado para indicar qual chave do cabeçalho deve ser modificada.
⚠️ IMPORTANTESe torna opcional se seu body for do tipo TEXTO e body.action tiver o valor
ADD
.
Campo obrigatório, do tipo string, utilizado como valor a ser usado para modificar a chave indicada no campo body.key.
Temos possibilidades de utilização de valores dinâmicos, e de variáveis de ambiente para esse campo.
⚠️ IMPORTANTESe torna opcional apenas se body.action tiver o valor
DEL
.
Campo opcional, do tipo booleano, o valor padrão é false
indicando que o modificador não deve propagar essa mudança
em questão para os backends seguintes.
Caso informado como true
essa modificação será propagada para os seguintes backends.
Campo opcional, do tipo objeto, é responsável pela customização da resposta recebida pelo serviço backend.
Campo opcional, do tipo string, campo livre para anotações.
Campo opcional, do tipo booleano, o valor padrão é false
, indica o desejo de omitir toda a resposta ao usuário final
HTTP.
Campo opcional, do tipo booleano, o valor padrão é false
, indica o desejo de omitir o cabeçalho da resposta.
Campo opcional, do tipo booleano, o valor padrão é false
, indica o desejo de omitir o corpo da resposta.
Campo opcional, do tipo string, é responsável por agregar todo o corpo da resposta em um campo JSON, veja no exemplo abaixo:
Corpo atual:
[
"test",
"value",
"array"
]
Configuração do campo group
com o valor test_list
que resultou:
{
"test_list": [
"test",
"value",
"array"
]
}
Campo opcional, é responsável por mapear os campos do cabeçalho de resposta, fazendo um de/para do nome do campo atual para o nome desejado, veja o exemplo:
Cabeçalho atual:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-Device-Id: asajlaks212
X-Test-Id: asdkmalsd123
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
Configuração do header-mapper:
{
"X-Value-Id": "X-New-Value-Id",
"X-Device-Id": "X-New-Device-Id",
"X-Test-Id": "X-New-Test-Id"
}
Resultado:
Content-Type: application/json
X-New-Value-Id: 4ae6c92d16089e521626
X-New-Device-Id: asajlaks212
X-New-Test-Id: asdkmalsd123
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
⚠️ IMPORTANTESó é permitido a customização de chaves não obrigatórias que não sejam:
- Content-Type
- Content-Encoding
- Content-Length
Campo opcional, do tipo mapa chave-valor string, é responsável por mapear os campos do corpo JSON da resposta, fazendo um de/para do nome do campo atual para o nome desejado, veja o exemplo:
Corpo atual da resposta:
{
"id": 1,
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
}
}
Configuração do body-mapper:
{
"firstName": "personalData.firstName",
"lastName": "personalData.lastName",
"age": "personalData.age"
}
Resultado:
{
"id": 1,
"personalData": {
"firstName": "John",
"lastName": "Smith",
"age": 25
},
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
}
}
Campo opcional, do tipo objeto, é responsável por customizar o envio de campos do cabeçalho de resposta do serviço backend.
Valores aceitos para os campos
-1
: Significa que você deseja remover o campo indicado.1
: Significa que você deseja manter o campo indicado.
Exemplo
Cabeçalho atual:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-Device-Id: asajlaks212
X-Test-Id: asdkmalsd
X-User-Id: 123
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
Configuração do header-projection:
{
"X-Value-Id": 1,
"X-User-Id": 1
}
Resultado:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-User-Id: 123
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
⚠️ IMPORTANTESó é permitido a customização de chaves não obrigatórias que não sejam:
- Content-Type
- Content-Encoding
- Content-Length
Campo opcional, do tipo objeto, é responsável por customizar o envio de dos campos do corpo JSON da resposta do serviço backend.
Valores aceitos para os campos
-1
: Significa que você deseja remover o campo indicado.1
: Significa que você deseja manter o campo indicado.
Exemplo
Corpo atual:
{
"id": 1,
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
}
}
Configuração do body-projection:
{
"age": -1,
"address.postalCode": -1
}
Resultado:
{
"id": 1,
"firstName": "John",
"lastName": "Smith",
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY"
}
}
Campo opcional, do tipo lista de objeto, valor padrão é vazio, é responsável por modificações especificas do cabeçalho de resposta do serviço backend.
Campo opcional, do tipo string, campo livre para anotações.
Campo obrigatório, do tipo string, responsável pela ação a ser tomada na modificação do cabeçalho da resposta.
Valores aceitos
-
ADD
: Adiciona a chave informada no campo header.key caso não exista, e agrega o valor informado no campo header.value. -
APD
: Acrescenta o valor informado no campo header.value caso a chave informada no campo header.key exista. -
SET
: Define o valor da chave informada no campo header.key pelo valor passado no campo header.value. -
RPL
: Substitui o valor da chave informada no campo header.key pelo valor passado no campo header.value caso exista. -
DEL
: Remove a chave informada no campo header.key caso exista.
Campo obrigatório, do tipo string, utilizado para indicar qual chave do cabeçalho deve ser modificada.
Campo obrigatório, do tipo string, utilizado como valor a ser usado para modificar a chave indicada no campo header.key.
Temos possibilidades de utilização de valores dinâmicos, e de variáveis de ambiente para esse campo.
⚠️ IMPORTANTESe torna opcional apenas se header.action tiver o valor
DEL
.
Campo opcional, do tipo lista de objeto, valor padrão é vazio, responsável pelas modificações do corpo da resposta do serviço backend.
Campo opcional, do tipo string, campo livre para anotações.
Campo obrigatório, do tipo string, responsável pela ação a ser tomada na modificação do corpo da resposta.
Valores aceitos se o corpo for do tipo JSON
-
ADD
: Adiciona a chave informada no campo body.key caso não exista, e agrega o valor informado no campo body.value. -
APD
: Acrescenta o valor informado no campo body.value caso a chave informada no campo body.key exista. -
SET
: Defini o valor da chave informada no campo body.key pelo valor passado no campo body.value. -
RPL
: Substitui o valor da chave informada no campo body.key pelo valor passado no campo body.value caso exista. -
REN
: Renomeia a chave informada no campo body.key pelo valor passado no campo body.value caso exista. -
DEL
: Remove a chave informada no campo body.key caso exista.
Valores aceitos se o corpo for TEXTO
-
ADD
: Agrega o valor informado no campo body.value ao texto. -
APD
: Acrescenta o valor informado no campo body.value caso body não for vazio. -
RPL
: Irá substituir todos os valores semelhantes à chave informada no campo body.key pelo valor passado no campo body.value. -
DEL
: Remove todos os valores semelhantes à chave informada no campo body.key.
Campo obrigatório, do tipo string, utilizado para indicar qual chave do cabeçalho deve ser modificada.
⚠️ IMPORTANTESe torna opcional se seu body for do tipo TEXTO e body.action tiver o valor
ADD
.
Campo obrigatório, do tipo string, utilizado como valor a ser usado para modificar a chave indicada no campo body.key.
Temos possibilidades de utilização de valores dinâmicos, e de variáveis de ambiente para esse campo.
⚠️ IMPORTANTESe torna opcional apenas se body.action tiver o valor
DEL
.
O Gopen API Gateway quando iniciado, gera um arquivo JSON, baseado no JSON de configuração,
localizado na pasta runtime
na raiz da sua aréa de trabalho.
Esse JSON, indica qual foi o entendimento da aplicação ao ler o JSON de configuração, todas as #variáveis de configuração já terão seus valores substituídos, caso exista.
Esse json também pode ser lido utilizando a rota estática /settings.
O Gopen API Gateway tem alguns endpoints estáticos, isto é, indepêndente de qualquer configuração feita, teremos atualmente três endpoints cadastrados nas rotas do mesmo, veja abaixo cada um e suas responsabilidades:
Endpoint para saber se a API Gateway está viva o path, retorna 404 (Not found)
se tiver off, e
200 (OK)
se tiver no ar.
Endpoint que retorna a versão obtida na config version, retorna 404 (Not Found)
se não tiver sido
informado no json de configuração, caso contrário retorna o 200 (OK)
com o valor no body
como texto.
Endpoint retorna algumas informações sobre o projeto, como versão, data da versão, quantidade de contribuintes e um resumo de quantos endpoints, middlewares, backends e modifiers configurados no momento e o json de configuração que está rodando ativamente.
{
"version": "v1.0.0",
"version-date": "03/27/2024",
"founder": "Gabriel Cataldo",
"contributors": 1,
"endpoints": 4,
"middlewares": 1,
"backends": 7,
"setting": {}
}
As variáveis de ambiente podem ser fácilmente instânciadas utilizando o arquivo .env, na pasta indicada pelo ambiente dinâmico de inicialização como mencionado no tópico ESTRUTURA DE PASTAS.
Caso preferir inserir os valores utilizando docker-compose também funcionará corretamente, ponto é que a API Gateway irá ler o valor gravado na máquina, independente de como foi inserido nela.
Os valores podem ser utilizados na configuração do JSON da API Gateway, basta utilizar a sintaxe $NOME
como
um valor string, veja no exemplo abaixo.
Um trecho de um JSON de configuração, temo os seguintes valores:
{
"version": "$VERSION",
"hot-reload": true,
"store": {
"redis": {
"address": "$REDIS_URL",
"password": "$REDIS_PASSWORD"
}
},
"timeout": "$TIMEOUT"
}
E na nossa máquina temos as seguintes variáveis de ambiente:
VERSION=1.0.0
REDIS_URL=redis-18885.c259.us-east-1-4.ec2.cloud.redislabs.com:18985
REDIS_PASSWORD=12345
TIMEOUT=5m
A API Gateway gera um arquivo de JSON de tempo de execução ao rodar a aplicação, veja o resultado do mesmo após iniciar a aplicação:
{
"version": "1.0.0",
"hot-reload": true,
"store": {
"redis": {
"address": "redis-18885.c259.us-east-1-4.ec2.cloud.redislabs.com:18985",
"password": "12345"
}
},
"timeout": "5m"
}
Vimos que todos os valores com a sintaxe $NOME
foram substituídos pelos seus devidos valores, caso um valor
tenha sido mencionado por essa sintaxe, porém não existe nas variáveis de ambiente, o mesmo valor informado
será mantido.
Podemos utilizar valores de requisição e resposta do tempo de execução do endpoint, conforme o mesmo foi configurado. Esses valores podem ser obtidos por uma sintaxe específica, temos as seguintes possibilidades de obtenção desses valores, veja:
Quando menciona a sintaxe #request...
você estará obtendo os valores da requisição recebida.
Esse trecho da sintaxe irá obter do cabeçalho da requisição o valor indicado, por exemplo,
#request.header.X-Forwarded-For.0
irá obter o primeiro valor do campo X-Forwarded-For
do cabeçalho da requisição
caso exista, substituindo a sintaxe pelo valor, o resultado foi 127.0.0.1
.
Esse trecho da sintaxe irá obter dos parâmetros da requisição o valor indicado, por exemplo,
#request.params.id
irá obter o valor do campo id
dos parâmetros da requisição caso exista,
substituindo a sintaxe pelo valor, o resultado foi 72761
.
Esse trecho da sintaxe irá obter dos parâmetros de busca da requisição o valor indicado, por exemplo,
#request.query.email.0
irá obter o primeiro valor do campo email
dos parâmetros de busca da requisição caso exista,
substituindo a sintaxe pelo valor, o resultado foi gabrielcataldo.adm@gmail.com
.
Esse trecho da sintaxe irá obter do body da requisição o valor indicado, por exemplo,
#request.body.deviceId
irá obter o valor do campo deviceId
do body da requisição caso exista,
substituindo a sintaxe pelo valor, o resultado foi 991238
.
Quando menciona a sintaxe #responses...
você estará obtendo os valores do histórico de respostas dos backends do
endpoint sendo beforewares, backends e afterwares
No exemplo, eu tenho apenas um backend e o mesmo foi processado, então posso está utilizando a sintaxe:
#responses.0.header.X-Value.0
Nesse outro exemplo de sintaxe temos três backends configurados e dois já foram processados, então podemos utilizar a seguinte sintaxe no processo do terceiro backend:
#responses.1.body.users.0
Nesses exemplos citados vemos que podemos obter o valor da resposta de um backend que já foi processado, e que estão armazenados em um tipo de histórico temporário.
Você pode utilizar com base nesses campos, a sintaxe de JSON path que se enquadra em seus valores, apenas se lembre que, os objetos header, query são mapas de lista de string, e o params é um mapa de string.
Aprenda na prática como utilizar os valores dinâmicos para modificação usando o projeto playground que já vem com alguns exemplos de modificadores com valores dinâmicos.
Quando utilizamos uma API Gateway nos perguntamos, como será retornado ao meu cliente a resposta desse endpoint configurado?
Para facilitar o entendimento criamos esse tópico para resumir a lógica de resposta da nossa API Gateway, então vamos começar.
A API Gateway foi desenvolvida com uma inteligência e flexibilidade ao responder um endpoint, ela se baseia em dois pontos importantes, primeiro, na quantidade de respostas de serviços backends que foram processados, e segundo, nos campos de customização da resposta configurados nos objetos endpoint e backend. Vamos ver alguns exemplos abaixo para melhor entendimento.
Nesse exemplo trabalharemos apenas com um único backend, veja como a API Gateway se comportará ao responder a esse cenário:
Json de configuração
{
"$schema": "https://raw.githubusercontent.com/tech4works/gopen-gateway/main/json-schema.json",
"endpoints": [
{
"path": "/users/find/:key",
"method": "GET",
"backends": [
{
"hosts": [
"$USER_SERVICE_URL"
],
"path": "/users/find/:key",
"method": "GET"
}
]
}
]
}
Ao processar esse endpoint a resposta da API Gateway foi:
HTTP/1.1 200 OK
Cabeçalho (Veja sobre os cabeçalhos de resposta aqui)
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: true
X-Gopen-Success: true
Date: Tue, 23 Apr 2024 11:37:26 GMT
Content-Length: 620
Corpo
{
"id": "6499b8826493f85e45eb3794",
"name": "Gabriel Cataldo",
"birthDate": "1999-01-21T00:00:00Z",
"gender": "MALE",
"currentPage": "HomePage",
"createdAt": "2023-06-26T16:10:42.265Z",
"updatedAt": "2024-03-10T20:19:03.452Z"
}
Vimos que no exemplo a API Gateway serviu como um proxy redirecionando a requisição para o serviço backend configurado e espelhando seu body de resposta, e agregando seus valores no cabeçalho de resposta.
Nesse mesmo exemplo vamos forçar um cenário de infelicidade na resposta do backend, veja:
HTTP/1.1 404 Not Found
Cabeçalho (Veja sobre os cabeçalhos de resposta aqui)
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: true
X-Gopen-Success: false
Date: Tue, 23 Apr 2024 21:56:33 GMT
Content-Length: 235
Corpo
{
"file": "datastore/user.go",
"line": 227,
"endpoint": "/users/find/gabrielcataldo.adma@gmail.com",
"message": "user not found"
}
Neste caso a API Gateway também espelhou a resposta da única chamada de backend do endpoint.
Nesse exemplo, vamos utilizar os middlewares de beforewares e afterwares, como esses backends são omitidos ao cliente final se tiverem sucesso, vamos simular uma chamada com o device bloqueado para que o beforeware retorne um erro, e depois um afterware que responderá também um erro, pois não existe, vamos lá!
Json de configuração
{
"$schema": "https://raw.githubusercontent.com/tech4works/gopen-gateway/main/json-schema.json",
"middlewares": {
"save-device": {
"hosts": [
"$DEVICE_SERVICE_URL"
],
"path": "/devices",
"method": "PUT"
},
"increment-attempts": {
"hosts": [
"$SECURITY_SERVICE_URL"
],
"path": "/attempts",
"method": "POST"
}
},
"endpoints": [
{
"path": "/users/find/:key",
"method": "GET",
"beforewares": [
"save-device"
],
"afterwares": [
"increment-attempts"
],
"backends": [
{
"hosts": [
"$USER_SERVICE_URL"
],
"path": "/users/find/:key",
"method": "GET"
}
]
}
]
}
Ao processar esse endpoint de exemplo simulando o erro na chamada de beforeware a resposta da API Gateway foi:
HTTP/1.1 403 Forbidden
Cabeçalho (Veja sobre os cabeçalhos de resposta aqui)
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: false
X-Gopen-Success: true
Date: Tue, 23 Apr 2024 23:02:09 GMT
Content-Length: 154
Corpo
{
"file": "service/device.go",
"line": 49,
"endpoint": "/devices",
"message": "unprocessed entity: device already exists and is not active"
}
Vimos que a resposta foi o espelho do retorno do beforeware save-device
, pois como o mesmo retornou
falha 403 (Forbidden)
, o endpoint abortou, não chamando os backends seguintes, lembrando que você
pode configurar os códigos de status HTTP que vão ser abortados pelo seu endpoint, basta preencher o
campo endpoint.abort-if-status-codes.
No seguinte exemplo iremos forçar um erro no afterware increment-attempts
a da API Gateway resposta foi:
HTTP/1.1 404 Not Found
Cabeçalho (Veja sobre os cabeçalhos de resposta aqui)
Content-Type: text/plain
X-Gopen-Cache: false
X-Gopen-Complete: true
X-Gopen-Success: false
Date: Tue, 23 Apr 2024 23:16:57 GMT
Content-Length: 18
Corpo
404 page not found
Vimos que a resposta também foi o espelho do retorno do afterware increment-attempts
, por mais que seja a última
chamada de um serviço backend do endpoint, pois caiu na regra de resposta abortada, então, todas as outras respostas
dos outros backends foram ignoradas e apenas foi retornado a resposta do backend abortado.
Veja mais sobre a resposta abortada.
Nesse exemplo iremos trabalhar com três backends principais no endpoint, então, vamos lá!
Json de configuração
{
"$schema": "https://raw.githubusercontent.com/tech4works/gopen-gateway/main/json-schema.json",
"port": 8080,
"endpoints": [
{
"path": "/users/find/:key",
"method": "GET",
"backends": [
{
"hosts": [
"$USER_SERVICE_URL"
],
"path": "/users/find/:key",
"method": "GET"
},
{
"hosts": [
"$DEVICE_SERVICE_URL"
],
"path": "/devices",
"method": "PUT"
},
{
"hosts": [
"$USER_SERVICE_URL"
],
"path": "/version",
"method": "GET",
"response": {
"group": "version"
}
}
]
}
]
}
No exemplo iremos executar os três backend com sucesso, a API Gateway respondeu
HTTP/1.1 200 OK
Cabeçalho (Veja sobre os cabeçalhos de resposta aqui)
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: true
X-Gopen-Success: true
Date: Tue, 23 Apr 2024 23:49:12 GMT
Content-Length: 755
Corpo
[
{
"ok": true,
"code": 200,
"id": "6499b8826493f85e45eb3794",
"name": "Gabriel Cataldo",
"birthDate": "1999-01-21T00:00:00Z",
"gender": "MALE",
"currentPage": "HomePage",
"createdAt": "2023-06-26T16:10:42.265Z",
"updatedAt": "2024-03-10T20:19:03.452Z"
},
{
"ok": true,
"code": 200,
"id": "661535275d6fc736d831c754",
"usersId": [
"6499b8826493f85e45eb3793"
],
"status": "ACTIVE",
"createdAt": "2024-04-09T12:31:35.907Z",
"updatedAt": "2024-04-23T23:49:12.759Z"
},
{
"ok": true,
"code": 200,
"version": "v1.0.0"
}
]
Temos alguns pontos nesse exemplo que vale ressaltar, primeiro com o formato, a API Gateway entendeu que seu endpoint
tem múltiplas respostas e não foi utilizado o campo endpoint.response.aggregate
com o valor true
, então ela lista as respostas como JSON acrescentando os seguintes campos:
ok
: Indica se a resposta do backend em questão teve o código de status HTTP entre 200
e 299
.
code
: É preenchido com código de status HTTP respondido pelo seu backend.
Esses campos são apenas acrescentado se houver múltiplas respostas e o
campo endpoint.response.aggregate não for informado com o valor true
.
Segundo ponto a destacar é no trecho "version": "v1.0.0"
do último backend, o mesmo respondeu apenas um texto no body
de resposta que foi v1.0.0
, porém para esse cenário como foi mencionado, a API Gateway força a conversão desse valor
para um JSON, adicionando um novo campo com o valor informado na
configuração endpoint.backend.response.group do mesmo.
Terceiro ponto é sobre o código de status HTTP, o mesmo é retornado pela maior frequência, isto é, se temos três
retornos 200 OK
como no exemplo a API Gateway também retornará esse código. Se tivermos um retorno igualitário o
último código de status HTTP retornado será considerado, veja os cenários possíveis dessa lógica:
[
{
"ok": true,
"code": 204
},
{
"ok": true,
"code": 200
},
{
"ok": true,
"code": 201
}
]
a API Gateway responderá 201 Created
.
[
{
"ok": true,
"code": 100
},
{
"ok": true,
"code": 100
},
{
"ok": true,
"code": 201
}
]
a API Gateway responderá 100 Continue
.
Quarto ponto a ser destacado, é que como o endpoint tem múltiplas respostas, consequentemente temos múltiplos cabeçalhos de resposta, a API Gateway irá agregar todos os campos e valores para o cabeçalho da resposta final, veja mais sobre o comportamento do cabeçalho de resposta clicando aqui.
Último ponto a ser destacado, é que caso um desses retornos de backend entre no cenário em que o endpoint aborta a resposta, ele não seguirá nenhuma diretriz mostrada no tópico em questão e sim lógica de resposta abortada.
Nesse exemplo iremos utilizar uma configuração parecida com JSON de configuração do exemplo acima, porém com
campo endpoint.response.aggregate com o valor true
.
Json de configuração
{
"$schema": "https://raw.githubusercontent.com/tech4works/gopen-gateway/main/json-schema.json",
"endpoints": [
{
"path": "/users/find/:key",
"method": "GET",
"response": {
"aggregate": true
},
"backends": [
{
"hosts": [
"$USER_SERVICE_URL"
],
"path": "/users/find/:key",
"method": "GET"
},
{
"hosts": [
"$DEVICE_SERVICE_URL"
],
"path": "/devices",
"method": "PUT"
},
{
"hosts": [
"$USER_SERVICE_URL"
],
"path": "/version",
"method": "GET",
"response": {
"group": "version"
}
}
]
}
]
}
Ao processarmos o endpoint a resposta da API Gateway foi:
HTTP/1.1 200 OK
Cabeçalho (Veja sobre os cabeçalhos de resposta aqui)
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: true
X-Gopen-Success: true
Date: Wed, 24 Apr 2024 10:57:31 GMT
Content-Length: 665
Corpo
{
"id": [
"6499b8826493f85e45eb3794",
"661535275d6fc736d831c754"
],
"name": "Gabriel Cataldo",
"gender": "MALE",
"currentPage": "HomePage",
"lastSeenAt": "2024-02-19T11:43:27.324Z",
"createdAt": [
"2024-04-09T12:31:35.907Z",
"2023-06-26T16:10:42.265Z"
],
"updatedAt": [
"2024-04-24T11:04:32.184Z",
"2024-03-10T20:19:03.452Z"
],
"usersId": [
"6499b8826493f85e45eb3793"
],
"status": "ACTIVE",
"version": "v1.0.0"
}
Vimos a única diferença de resposta do tópico Múltiplos backends é que ele agregou os valores de todas as respostas em um só JSON, e os campos que se repetiram foram agregados os valores em lista.
As demais regras como código de status HTTP, a conversão forçada para JSON, entre outras, seguem as mesmas regras mencionadas no tópico Múltiplos backends.
No exemplo podemos deixar a resposta agregada um pouco mais organizada, com isso vamos alterar o trecho do nosso
segundo backend adicionando o campo endpoint.backend.response.group com o
valor device
, veja o trecho do JSON de configuração modificado:
{
"hosts": [
"$DEVICE_SERVICE_URL"
],
"path": "/devices",
"method": "PUT",
"response": {
"group": "device"
}
}
Ao processar novamente o endpoint obtivemos a seguinte resposta:
HTTP/1.1 200 OK
Cabeçalho (Veja sobre os cabeçalhos de resposta aqui)
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: true
X-Gopen-Success: true
Date: Wed, 24 Apr 2024 11:23:07 GMT
Content-Length: 697
Corpo
{
"id": "6499b8826493f85e45eb3793",
"name": "Gabriel Cataldo",
"birthDate": "1999-01-21T00:00:00Z",
"gender": "MALE",
"currentPage": "HomePage",
"lastSeenAt": "2024-02-19T11:43:27.324Z",
"createdAt": "2023-06-26T16:10:42.265Z",
"updatedAt": "2024-03-10T20:19:03.452Z",
"device": {
"id": "661535275d6fc736d831c754",
"usersId": [
"6499b8826493f85e45eb3793"
],
"status": "ACTIVE",
"createdAt": "2024-04-09T12:31:35.907Z",
"updatedAt": "2024-04-24T11:23:07.832Z"
},
"version": "v1.0.0"
}
Com essa configuração vimos que nossa resposta agregada ficou mais organizada, e como é importante entender sobre o json de configuração e seus campos, para que o GOPEN API Gateway atenda melhor suas necessidades.
Para uma resposta ser abortada pela API Gateway, um dos backends configurados do endpoint tanto middlewares como os principais, ao serem processados, na sua resposta, o código de status HTTP precisa seguir valores no campo endpoint.abort-if-status-codes do próprio endpoint.
IMPORTANTE
Ao abortar a resposta do backend, a API Gateway irá espelhar apenas a resposta do mesmo, código de status, cabeçalho e corpo, sendo assim, as outras respostas já processadas serão ignoradas.
Indicamos utilizar essa configuração apenas quando algo fugiu do esperado, como, por exemplo, uma resposta
500 (Internal server error)
.
Na resposta, a API Gateway com exceção dos campos Content-Length
, Content-Type
e Date
agrega todos valores de
cabeçalho respondidos pelos backends configurados no endpoint, indepêndente da quantidade de backends.
Também são adicionados até quatro campos no cabeçalho veja abaixo sobre os mesmos:
-
X-Gopen-Timeout
: Enviado na requisição ao backend, ele contém o tempo restante para o processamento em milissegundos, com o mesmo dá para implementar um contexto com timeout linear nos seus microserviços, evitando vazamento de processos, já que após esse tempo a API Gateway retornara 504 (Gateway Timeout). -
X-Gopen-Cache
: Caso a resposta do endpoint não seja "fresca", isto é, foi utilizado a resposta armazenada em cache, é retornado o valortrue
, caso contrário retorna o valorfalse
. -
X-Gopen-Cache-Ttl
: Caso a resposta do endpoint tenha sido feita utilizando o armazenamento em cache, ele retorna a duração do tempo de vida restante desse cache, caso contrário o campo não é retornado. -
X-Gopen-Complete
: Caso todos os backends tenham sido processados pelo endpoint é retornado o valortrue
, caso contrário é retornado o valorfalse
. -
X-Gopen-Success
: Caso todos os backends tenham retornado sucesso, isto é, o código de status HTTP de resposta entre200
a299
, ele retorna o valortrue
, caso contrário o valorfalse
.
Lembrando que se a resposta de um backend for abortada, apenas o header do mesmo é agregado e considerado as regras dos campos acima.
Agora vamos ver alguns exemplos de cabeçalho de retorno:
Cabeçalho de resposta do backend 1:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-MS: api-user
Date: Wed, 24 Apr 2024 11:23:07 GMT
Content-Length: 102
Cabeçalho de resposta do endpoint
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-MS: api-user
X-Gopen-Cache: false
X-Gopen-Complete: true
X-Gopen-Success: true
Date: Wed, 24 Apr 2024 11:23:08 GMT
Content-Length: 102
Vimos que no exemplo foram adicionados os campos padrões, e agregado os valores do
cabeçalho de resposta, que foram X-Value-Id
e X-MS
.
Cabeçalho de resposta do backend 1:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626
X-MS: api-user
Date: Wed, 24 Apr 2024 11:23:07 GMT
Content-Length: 102
Cabeçalho de resposta do backend 2:
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521638
X-MS: api-device
X-MS-Success: true
Date: Wed, 24 Apr 2024 11:23:08 GMT
Content-Length: 402
Cabeçalho de resposta do endpoint
Content-Type: application/json
X-Value-Id: 4ae6c92d16089e521626, 4ae6c92d16089e521638
X-MS: api-user, api-device
X-MS-Success: true
X-Gopen-Cache: false
X-Gopen-Complete: true
X-Gopen-Success: true
Date: Wed, 24 Apr 2024 11:23:09 GMT
Content-Length: 504
Vimos que no exemplo também foram adicionados os campos padrões, e agregado os valores do
cabeçalho de resposta, que foram X-Value-Id
, X-MS
e X-MS-Success
, vale ressaltar que os campos que se repetiram
foram agrupados e separados por vírgula.
Toda API Gateway tem suas respostas padrão para cada cenário de erro, então iremos listar abaixo cada cenário e sua respectiva resposta HTTP:
Esse cenário acontece quando todos os backends forem preenchidos com a configuração
endpoint.backend.response.omit como true
e o endpoint foi processado
corretamente,
porém não há nada a ser retornado.
Esse cenário acontece quando o tamanho do corpo de requisição é maior do que o permitido para o endpoint, utilizando a
configuração limiter.max-body-size para corpo normal
e limiter.max-multipart-memory-size para envio do tipo form-data
. Você pode
customizar essa configuração para um endpoint específico utilizando o campo endpoint.limiter.
Cabeçalho
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: false
X-Gopen-Success: false
Date: Fri, 26 Apr 2024 11:56:06 GMT
Content-Length: 170
Corpo
{
"file": "infra/size_limiter.go",
"line": 92,
"endpoint": "/users",
"message": "payload too large error: permitted limit is 1.0B",
"timestamp": "2024-04-26T08:56:06.628636-03:00"
}
Esse cenário acontece quando o limite de requisições são atingidas por um determinado IP, esse limite é definido na configuração limiter.rate. Você pode customizar essa configuração para um endpoint específico utilizando o campo endpoint.limiter.
Cabeçalho
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: false
X-Gopen-Success: false
Date: Fri, 26 Apr 2024 12:12:53 GMT
Content-Length: 177
Corpo
{
"file": "infra/rate_limiter.go",
"line": 100,
"endpoint": "/users",
"message": "too many requests error: permitted limit is 1 every 1s",
"timestamp": "2024-04-26T09:12:53.501804-03:00"
}
Esse cenário acontece quando o tamanho do header é maior do que o permitido para o endpoint, utilizando a configuração limiter.max-header-size. Você pode customizar essa configuração para um endpoint específico utilizando o campo endpoint.limiter.
Cabeçalho
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: false
X-Gopen-Success: false
Date: Fri, 26 Apr 2024 11:39:53 GMT
Content-Length: 186
Corpo
{
"file": "infra/size_limiter.go",
"line": 80,
"endpoint": "/multiple/backends/:key",
"message": "header too large error: permitted limit is 1.0B",
"timestamp": "2024-04-26T08:39:53.944055-03:00"
}
Esse cenário é específico quando algum erro inesperado ocorreu com a API Gateway, caso isso aconteça relate o problema aqui mostrando a resposta e o log impresso no terminal de execução.
Cabeçalho
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: false
X-Gopen-Success: false
Date: Fri, 26 Apr 2024 12:38:16 GMT
Content-Length: 183
Corpo
{
"file": "middleware/panic_recovery.go",
"line": 27,
"endpoint": "/users",
"message": "gateway panic error occurred! detail: runtime error: invalid memory address or nil pointer dereference",
"timestamp": "2024-04-26T09:42:23.938997-03:00"
}
Esse cenário acontece quando ao tentar se comunicar com o backend, e ocorre alguma falha de comunicação com o mesmo.
Cabeçalho
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: false
X-Gopen-Success: false
Date: Thu, 25 Apr 2024 01:07:36 GMT
Content-Length: 277
Corpo
{
"file": "infra/rest.go",
"line": 69,
"endpoint": "/users/find/:key",
"message": "bad gateway error: Get \"http://192.168.1.8:8090/users/find/gabrielcataldo.adm@gmail.com\": dial tcp 192.168.1.8:8090: connect: connection refused",
"timestamp": "2024-04-24T22:07:36.558851-03:00"
}
Esse cenário acontece quando o endpoint excede o limite do tempo configurado no campo timeout. Você pode customizar essa configuração para um endpoint específico utilizando o campo endpoint.timeout.
Cabeçalho
Content-Type: application/json
X-Gopen-Cache: false
X-Gopen-Complete: false
X-Gopen-Success: false
Date: Fri, 26 Apr 2024 13:29:55 GMT
Content-Length: 150
Corpo
{
"file": "middleware/timeout.go",
"line": 81,
"endpoint": "/users/version",
"message": "gateway timeout: 5m",
"timestamp": "2024-04-26T10:29:55.908526-03:00"
}
O Gopen API Gateway tem por padrão uma integração com Elastic, podendo utilizar alguns serviços veja:
Vale destacar que preservamos e enviamos o trace para os serviços backend subjacentes utilizando o Elastic APM Trace,
e sempre adicionamos o campo X-Forwarded-For
com o IP do client.
-
Playground um repositório para começar a explorar e aprender na prática!
-
Base um repositório para começar o seu novo projeto, apenas com o necessário!
Ficamos felizes quando vemos a comunidade se apoiar, e projetos como esse, está de braços abertos para receber suas ideias, veja abaixo como participar.
Para conseguir rodar o projeto primeiro faça o download da linguagem Go versão 1.22 ou superior na sua máquina.
Com o Go instalado na sua máquina, faça o pull do projeto
git pull https://github.com/tech4works/gopen-gateway.git
Depois abra o mesmo usando o próprio terminal com a IDE de sua preferência
Goland:
goland gopen-gateway
VSCode:
code gopen-gateway
Para inicializar o desenvolvimento, você pode criar uma branch a partir da main, para um futuro PR para a mesma.
Esse projeto teve apoio de bibliotecas fantásticas, esse trecho dedico a cada uma listada abaixa:
- checker
- converter
- errors
- fsnotify
- gin
- gjson
- sjson
- mxj
- strcase
- uuid
- godotenv
- gojsonschema
- go-redis
- ttlcache
Obrigado por contribuir para a comunidade Go e facilitar o desenvolvimento desse projeto.