---
title: "Parte 3: Como Construir um API Gateway de Microservices Usando OpenResty - API7.ai"
description: "São apresentados exemplos mínimos de como executar um API gateway via configuração e inicialização do NGINX, correspondência de rotas e plugins, carregamento de plugins e escrita de plugins."
url: "https://api7.ai/pt/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3"
---

# Parte 3: Como Construir um API Gateway de Microservices Usando OpenResty

API7.ai

February 3, 2023

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

Neste artigo, a construção do gateway de API de microsserviços chega ao fim. Vamos usar um exemplo mínimo para juntar os componentes selecionados anteriormente e executá-los de acordo com o projeto desenhado!

## Configuração e inicialização do NGINX

Sabemos que o gateway de API é usado para lidar com a entrada de tráfego, então primeiro precisamos fazer uma configuração simples no `nginx.conf` para que todo o tráfego seja tratado pelo código Lua do gateway.

```
server {
    listen 9080;

    init_worker_by_lua_block {
        apisix.http_init_worker()
    }

    location / {
        access_by_lua_block {
            apisix.http_access_phase()
        }
        header_filter_by_lua_block {
            apisix.http_header_filter_phase()
        }
        body_filter_by_lua_block {
            apisix.http_body_filter_phase()
        }
        log_by_lua_block {
            apisix.http_log_phase()
        }
    }
}
```

Aqui usamos o gateway de API de código aberto [Apache APISIX](https://github.com/apache/apisix) como exemplo, então o exemplo de código acima contém a palavra-chave `apisix`. Neste exemplo, escutamos a porta `9080` e interceptamos todas as solicitações para essa porta por `location /`, processando-as através das fases `access`, `rewrite`, `header filter`, `body filter` e `log`, chamando as funções correspondentes dos plugins em cada fase. A fase `rewrite` é combinada na função `apisix.http_access_phase`.

A inicialização do sistema é tratada na fase `init_worker`, que inclui a leitura dos parâmetros de configuração, a pré-definição do diretório no etcd, a obtenção da lista de plugins do etcd e a ordenação dos plugins por prioridade, etc. Liste e expliquei as partes principais do código aqui, e você pode ver uma [função de inicialização](https://github.com/apache/apisix/blob/master/apisix/init.lua#L106) mais completa no GitHub.

```
function _M.http_init_worker()
    -- Inicialização de Roteamento, Serviços e Plugins - as três partes mais importantes
    router.init_worker()
    require("apisix.http.service").init_worker()
    require("apisix.plugins.ext-plugin.init").init_worker()
end
```

Como você pode ver neste código, a inicialização das partes do roteador e do plugin é um pouco mais complicada, principalmente envolvendo a leitura dos parâmetros de configuração e a tomada de algumas decisões dependendo deles. Como isso envolve a leitura de dados do etcd, usamos `ngx.timer` para contornar a restrição de "não poder usar cosocket na fase `init_worker`". Se você estiver interessado nesta parte, recomendamos a leitura do código-fonte para entender melhor.

## Correspondência de Rotas

No início da fase `access`, primeiro precisamos corresponder a rota com base no `uri`, `host`, `args`, `cookies`, etc., carregados pela solicitação, às regras de roteamento que foram configuradas.

```
router.router_http.match(api_ctx)
```

A única linha de código exposta ao público é a acima, onde o `api_ctx` armazena as informações de `uri`, `host`, `args` e `cookie` da solicitação. A [implementação](https://github.com/apache/apisix/blob/master/apisix/http/router/radixtree_uri.lua) específica da função de correspondência usa o `lua-resty-radixtree` que mencionamos anteriormente. Se nenhuma rota for correspondida, a solicitação não terá um upstream correspondente e retornará `404`.

```
local router = require("resty.radixtree")

local match_opts = {}

function _M.match(api_ctx)
    -- Obtém os parâmetros da solicitação do ctx e os usa como condição de julgamento para a rota
    match_opts.method = api_ctx.var.method
    match_opts.host = api_ctx.var.host
    match_opts.remote_addr = api_ctx.var.remote_addr
    match_opts.vars = api_ctx.var
    -- Chama a função de julgamento da rota
    local ok = uri_router:dispatch(api_ctx.var.uri, match_opts, api_ctx)
    -- Se nenhuma rota for correspondida, retorna 404
    if not ok then
        core.log.info("not find any matched route")
        return core.response.exit(404)
    end

    return true
end
```

## Carregamento de Plugins

Claro, se a rota for atingida, passamos para a etapa de filtragem e carregamento do plugin, que é o núcleo do gateway de API. Vamos começar com o seguinte código.

```
local plugins = core.tablepool.fetch("plugins", 32, 0)
-- A lista de plugins no etcd e a lista de plugins no arquivo de configuração local são intersectadas
api_ctx.plugins = plugin.filter(route, plugins)

-- Executa as funções montadas pelo plugin nas fases rewrite e access em sequência
run_plugin("rewrite", plugins, api_ctx)
run_plugin("access", plugins, api_ctx)
```

Neste código, primeiro solicitamos uma tabela de comprimento 32 através do pool de tabelas, que é uma técnica de otimização de desempenho que introduzimos anteriormente. Em seguida, vem a função de filtro do plugin. Você pode se perguntar por que essa etapa é necessária. Na fase init worker do plugin, não obtivemos a lista de plugins do etcd e os ordenamos?

A filtragem aqui é feita em comparação com a configuração local pelos seguintes dois motivos:

1. Primeiro, um plugin recém-desenvolvido precisa ser lançado em canário. Nesse momento, o novo plugin existe na lista do etcd, mas está apenas no estado aberto em alguns dos nós do gateway. Portanto, precisamos fazer uma operação de interseção adicional.
2. Para suportar o modo de depuração. Quais plugins são processados pela solicitação do cliente? Qual é a ordem de carregamento desses plugins? Essas informações serão úteis ao depurar, então a função de filtro também determinará se está no modo de depuração e registrará essas informações no cabeçalho da resposta.

Então, no final da fase `access`, pegamos esses plugins filtrados e os executamos um por um em ordem de prioridade, como mostrado no código a seguir.

```
local function run_plugin(phase, plugins, api_ctx)
    for i = 1, #plugins, 2 do
        local phase_fun = plugins[i][phase]
        if phase_fun then
            -- O código de chamada principal
            phase_fun(plugins[i + 1], api_ctx)
        end
    end

    return api_ctx
end
```

Ao iterar pelos plugins, você pode ver que fazemos isso em intervalos de `2`. Isso ocorre porque cada plugin terá dois componentes: o objeto do plugin e os parâmetros de configuração do plugin. Agora, vamos olhar para a linha de código principal no exemplo de código acima.

```
phase_fun(plugins[i + 1], api_ctx)
```

Se essa linha de código for um pouco abstrata, vamos substituí-la por um plugin `limit_count` concreto, o que ficará muito mais claro.

```
limit_count_plugin_rewrite_function(conf_of_plugin, api_ctx)
```

Neste ponto, estamos praticamente concluídos com o fluxo geral do gateway de API. Todo esse código está no mesmo [arquivo](https://github.com/apache/apisix/blob/master/apisix/init.lua), que contém mais de 400 linhas de código, mas o núcleo do código são as poucas dezenas de linhas que descrevemos acima.

## Escrita de Plugins

Agora, há uma coisa a fazer antes que uma demonstração completa possa ser executada, e isso é escrever um plugin. Vamos pegar o plugin `limit-count` como exemplo. A implementação completa tem pouco mais de 60 linhas de código, que você pode ver clicando no [link](https://github.com/apache/apisix/blob/master/apisix/plugins/limit-count.lua). Aqui, vou explicar as linhas de código-chave em detalhes:

Primeiro, vamos introduzir `lua-resty-limit-traffic` como a biblioteca base para limitar o número de solicitações.

```
local limit_count_new = require("resty.limit.count").new
```

Em seguida, usando o `json schema` em `rapidjson` para definir quais são os parâmetros deste plugin:

```
local schema = {
    type = "object",
    properties = {
        count = {type = "integer", minimum = 0},
        time_window = {type = "integer", minimum = 0},
        key = {type = "string",
        enum = {"remote_addr", "server_addr"},
        },
        rejected_code = {type = "integer", minimum = 200, maximum = 600},
    },
    additionalProperties = false,
    required = {"count", "time_window", "key", "rejected_code"},
}
```

Esses parâmetros do plugin correspondem à maioria dos parâmetros do `resty.limit.count`, que contêm a chave do limite, o tamanho da janela de tempo e o número de solicitações a serem limitadas. Além disso, o plugin adiciona um parâmetro: `rejected_code`, que retorna o código de status especificado quando a solicitação é limitada.

Na etapa final, montamos a função de manipulação do plugin na fase `rewrite`:

```
function _M.rewrite(conf, ctx)
    -- Obtém o objeto de limite de contagem do cache, se não, usa a função `create_limit_obj` para criar um novo objeto e armazená-lo em cache
    local lim, err = core.lrucache.plugin_ctx(plugin_name, ctx,  create_limit_obj, conf)

    -- Obtém o valor da chave de `ctx.var` e compõe uma nova chave junto com o tipo de configuração e o número da versão da configuração
    local key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version

    -- Função para determinar se o limite é atingido
    local delay, remaining = lim:incoming(key, true)
    if not delay then
        local err = remaining
        -- Se o valor limite for excedido, retorna o código de status especificado
        if err == "rejected" then
            return conf.rejected_code
        end

        core.log.error("failed to limit req: ", err)
        return 500
    end

    -- Se o limite não for excedido, libera e define o cabeçalho de resposta correspondente
    core.response.set_header("X-RateLimit-Limit", conf.count,
                             "X-RateLimit-Remaining", remaining)
end
```

Há apenas uma linha de lógica no código acima que faz a determinação do limite, o resto está aqui para fazer o trabalho de preparação e definir os cabeçalhos de resposta. Se o limite não for excedido, ele continuará a executar o próximo plugin de acordo com a prioridade.

## Resumo

Finalmente, vou deixar uma pergunta para reflexão. Sabemos que os gateways de API podem lidar não apenas com tráfego de camada 7, mas também com tráfego de camada 4. Com base nisso, você consegue pensar em alguns cenários de uso para ele? Bem-vindo a deixar seus comentários e compartilhar este artigo para aprender e se comunicar com mais pessoas.

Anterior: [Parte 1: Como construir um gateway de API de microsserviços usando OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservice-api-gateway-part-1) [Parte 2: Como construir um gateway de API de microsserviços usando OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservice-api-gateway-part-2)

Share article link

Share

Related Posts

[![FAQ do OpenResty | Como o OpenResty é usado na prática](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [FAQ do OpenResty | Como o OpenResty é usado na prática](https://api7.ai/learning-center/openresty/faq-5)

[![Parte 2: Como construir um gateway de API para Microservices usando OpenResty](https://static.api7.ai/uploads/2023/02/07/FDLX3VMh_44.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservice-api-gateway-part-2)

## [Parte 2: Como construir um gateway de API para Microservices usando OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservice-api-gateway-part-2)

Table of Contents

- [Configuração e inicialização do NGINX](#configura%C3%A7%C3%A3o-e-inicializa%C3%A7%C3%A3o-do-nginx)
- [Correspondência de Rotas](#correspond%C3%AAncia-de-rotas)
- [Carregamento de Plugins](#carregamento-de-plugins)
- [Escrita de Plugins](#escrita-de-plugins)
- [Resumo](#resumo)
