O Kong, segundo a própia documentação , é um apigateway open source, criado para a nuvem, agnóstico em plataforma, feito para alta performance e extensível via plugins. O Kong possui uma grande gama de plugins oficiais que nos permitem fazer grande parte das customizações que necessitamos e quando o plugin não está disponível, podemos criar nosso própio plugin.
A linguagem padrão para se criar plugins é Lua, contudo outras linguagens são suportadas e uma delas é Go. Pessoalmente, não tenho experiência em escrever códigos em Lua, portanto, utilizar Go me permite uma maior velocidade de desenvolvimento e maior qualidade no plugin.
Desenvolvimento
Criar um plugin usando Go é bem simples, é necessário seguir a assinatura proposta pelo plugin development kit , ou PDK, do Kong, mas ela é de fácil entendimento. Para demonstrar isso, vamos criar um plugin que vai ler um header da requisição e a configuração do plugin. Com essas duas informações, vamos adicionar um novo header na resposta.
A primeira coisa a ser feita é definir as constantes version
e priority
. Como o própio nome diz, isso define a versão do plugin que está sendo desenvolvido e a prioridade em execução a relação a outros plugins. Vale a pena pontuar que quanto maior o valor da prioridade, mais cedo o plugin vai ser executado. Para ver mais sobre a prioridades dos plugins, este post
explica bem.
var Version = "0.0.1"
var Priority = 1
Com isso realizado, vamos definir quais os parametros que o plugin aceita, criando uma struct
chamada Config
. No nosso exemplo, aceitamos o campo message
, que é uma string.
type Config struct {
Message string `json:"message"`
}
Os plugins do Kong funcionam baseados em quais fases do ciclo de vida da requisição eles devem ser executados. As fases possíveis são:
- Certificate
- Rewrite
- Access
- Response
- Preread
- Log
No nosso exemplo, vamos utilizar o Access
, pois vamos atualizar a resposta antes de encaminharmos a requisição para o serviço responsável. Contudo, a assinatura de todas as fases são iguais. Para entender mais sobre as fases, recomendo ler essa documentação
.
func (conf Config) Access(kong *pdk.PDK) {
host, err := kong.Request.GetHeader("host")
if err != nil {
log.Printf("Error reading 'host' header: %s", err.Error())
}
message := conf.Message
if message == "" {
message = "hello"
}
kong.Response.SetHeader("x-hello-from-go", fmt.Sprintf("Go says %s to %s", message, host))
}
Essa função utiliza o PDK para ler os headers disponíveis, através de kong.Request.GetHeader
e depois adiciona um header na resposta utilizando o kong.Response.SetHeader
. Para mais informações dos funções dísponíveis, dê uma olhada na documentação da Go PDK
.
Por fim, vamos a função principal:
package main
import (
"fmt"
"log"
"github.com/Kong/go-pdk"
"github.com/Kong/go-pdk/server"
)
func main() {
server.StartServer(New, Version, Priority)
}
func New() interface{} {
return &Config{}
}
Como podemos notar, a funcão principal só inicializa o servidor do Kong, passando a Config
, Version
e Priority
definidas anteriormente. A implementação completa fica assim:
package main
import (
"fmt"
"log"
"github.com/Kong/go-pdk"
"github.com/Kong/go-pdk/server"
)
var Version = "0.0.1"
var Priority = 1
func main() {
server.StartServer(New, Version, Priority)
}
type Config struct {
Message string `json:"message"`
}
func New() interface{} {
return &Config{}
}
func (conf Config) Access(kong *pdk.PDK) {
host, err := kong.Request.GetHeader("host")
if err != nil {
log.Printf("Error reading 'host' header: %s", err.Error())
}
message := conf.Message
if message == "" {
message = "hello"
}
kong.Response.SetHeader("x-hello-from-go", fmt.Sprintf("Go says %s to %s", message, host))
}
Testes
Testar os plugins em Go também é bem simples, uma vez que o própio PDK nos oferece ferramentas que nos permitem fazer isso de forma direta. Então vamos testar nosso plugin:
package main
import (
"testing"
"github.com/Kong/go-pdk/test"
"github.com/stretchr/testify/assert"
)
func TestPluginWithoutConfig(t *testing.T) {
env, err := test.New(t, test.Request{
Method: "GET",
Url: "http://example.com?q=search&x=9",
Headers: map[string][]string{"host": {"localhost"}},
})
assert.NoError(t, err)
env.DoHttps(&Config{})
assert.Equal(t, 200, env.ClientRes.Status)
assert.Equal(t, "Go says hello to localhost", env.ClientRes.Headers.Get("x-hello-from-go"))
}
func TestPluginWithConfig(t *testing.T) {
env, err := test.New(t, test.Request{
Method: "GET",
Url: "http://example.com?q=search&x=9",
Headers: map[string][]string{"host": {"localhost"}},
})
assert.NoError(t, err)
env.DoHttps(&Config{Message: "nice to meet you"})
assert.Equal(t, 200, env.ClientRes.Status)
assert.Equal(t, "Go says nice to meet you to localhost", env.ClientRes.Headers.Get("x-hello-from-go"))
}
Deploy
Para o deploy, precisamos gerar um executável e colocar isso na imagem que vamos utilizar do Kong.
# Build Golang plugins
FROM golang:1.20 AS plugin-builder
WORKDIR /builder
COPY ./hello ./go_plugins/hello
RUN find ./go_plugins -maxdepth 1 -mindepth 1 -type d -not -path "*/.git*" | \
while read dir; do \
cd $dir && go build -o /builds/$dir main.go ; \
done
# Build Kong
FROM kong:3.4.0-ubuntu
COPY --from=plugin-builder ./builds/go_plugins/ ./kong/
USER kong
Para o Kong conseguir encontrar e carregar o plugin customizado é necessário especificar algumas configurações. Seguindo nosso exemplo, alterei o docker-compose
para incluir:
KONG_PLUGINS: "bundled,hello"
KONG_PLUGINSERVER_NAMES: hello
KONG_PLUGINSERVER_HELLO_START_CMD: /kong/hello
KONG_PLUGINSERVER_HELLO_QUERY_CMD: /kong/hello -dump
Considerações sobre performance
A própia Kong fez um estudo sobre a performance, que pode ser encontrado aqui .
Conclusão
Go é uma ótima ferramenta para se escrever plugins para o Kong. Podemos contar com toda a facilidade e performance da linguagem, além de todas as ferramentas que a linguagem oferece. No meu caso, consegui ganhar velocidade na entrega e uma maior cobertura de testes. Também é possível isolar a lógica dos plugins com a utilização da PDK, facilitando assim escrever plugins que funcionam pra diversas tecnologia, desacoplando do Kong.
Para ver todo o contéudo apresentado nesse post, é só acessar o repositório .
Você também pode me encontrar no Twitter , Github ou LinkedIn .