This article will explain in detail how to use Go to develop Apache plugins. By embracing the Go ecosystem and breaking new ground for Apache , we hope that Go will make Apache even better!
Why Go
Apache allows users to extend functionality by way of plugins. Core features such as authentication, flow restriction, request rewriting, etc. are implemented by way of plugins. Although the core code of Apache is written in Lua, Apache supports multi-language development of plugins, such as Go, Java.
This article will explain in detail how to develop Apache plugins in Go. By embracing the Go ecosystem, we are breaking new ground for Apache , and we hope that Go will make Apache even better!
Installation
To use Go Runner as a library, the official cmd/go-runner
example in -go-plugin-runner shows how to use the Go Runner SDK. The Go Runner SDK will also support loading pre-compiled plugins via the Go Plugin mechanism in the future.
Development
Developing with the Go Runner SDK
$ tree cmd/go-runnercmd/go-runner├── main.go├── main_test.go├── plugins│ ├── say.go│ └── say_test.go└── version.go
Above is the directory structure of the official example. main.go
is the entry point, where the most critical part is.
cfg := runner.RunnerConfig{}...runner.Run(cfg)
RunnerConfig
can be used to control the log level and log output location.
runner.Run
will make the application listen to the target location, receive requests and execute the registered plugins. The application will remain in this state until it exits.
Open plugins/say.go
.
func init() { err := plugin.RegisterPlugin(&Say{}) if err != nil { log.Fatalf("failed to register plugin say: %s", err) }}
Since main.go
imports the plugins package, the
import ( ... _ "github.com/apache/-go-plugin-runner/cmd/go-runner/plugins" ...)
This registers Say
with plugin.RegisterPlugin
before executing runner.Run
.
Say
needs to implement the following methods.
The Name
method returns the plugin name.
func (p *Say) Name() string { return "say"}
ParseConf
will be called when the plugin configuration changes, parsing the configuration and returning a plugin-specific configuration context.
func (p *Say) ParseConf(in []byte) (interface{}, error) { conf := SayConf{} err := json.Unmarshal(in, &conf) return conf, err}
The context of the plugin looks like this.
type SayConf struct { Body string `json:"body"`}
Filter
is executed on every request with the say plugin configured.
func (p *Say) Filter(conf interface{}, w http.ResponseWriter, r pkgHTTP.Request) { body := conf.(SayConf).Body if len(body) == 0 { return } w.Header().Add("X-Resp-A6-Runner", "Go") _, err := w.Write([]byte(body)) if err != nil { log.Errorf("failed to write: %s", err) }}
You can see that Filter takes the value of the body inside the configuration as the response body. If the response is made directly in the plugin, it will break the request.
Go Runner SDK API documentation: https://pkg.go.dev/github.com/apache/-go-plugin-runner
After building the application (make build
in the example), you need to set two environment variables at runtime.
_LISTEN_ADDRESS=unix:/tmp/runner.sock
_CONF_EXPIRE_TIME=3600
Like this:
_LISTEN_ADDRESS=unix:/tmp/runner.sock _CONF_EXPIRE_TIME=3600 ./go-runner run
The application will listen to /tmp/runner.sock
when it runs.
Setting up Apache (development)
The first step is to install Apache , which needs to be on the same instance as Go Runner.
The above diagram shows the workflow of Apache on the left, and the plugin runner on the right is responsible for running external plugins written in different languages. -go-plugin-runner is such a runner that supports the Go language.
When you configure a plugin runner in Apache , Apache treats the plugin runner as a child of itself, which belongs to the same user as the Apache process, and when we restart or reload Apache , the plugin runner will be restarted as well.
If the ext-plugin-* plugin is configured for a given route, a request to hit that route will trigger Apache to make an RPC call to the plugin runner over a unix socket. The call is broken down into two phases.
- ext-plugin-pre-req: before executing most of the Apache built-in plugins (Lua language plugins)
- ext-plugin-post-req: after the execution of the Apache built-in plugins (Lua language plugins)
Configure the timing of plugin runner execution as needed.
The plugin runner processes the RPC call, creates a simulated request inside it, then runs the plugins written in other languages and returns the results to Apache .
The order of execution of these plugins is defined in the ext-plugin-* plugin configuration entry. Like other plugins, they can be enabled and redefined on the fly.
To show how to develop Go plugins, we first set up Apache to enter development mode. Add the following configuration to config.yaml.
ext-plugin: path_for_test: /tmp/runner.sock
This configuration means that after the routing rule is hit, Apache will make an RPC request to /tmp/runner.sock.
Next, set up the routing rules.
curl http://127.0.0.1:9080//admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{ "uri": "/get", "plugins": { "ext-plugin-pre-req": { "conf": [ {"name":"say", "value":"{\"body\":\"hello\"}"} ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } }}<span class="token string" style="color: r