package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
)
// MyPluginResponse intercepts response from upstream
func MyPluginResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) {
// add a header to our response object
res.Header.Add("X-Response-Added", "resp-added")
// overwrite our response body
var buf bytes.Buffer
buf.Write([]byte(`{"message":"Hi! I'm a response plugin"}`))
res.Body = ioutil.NopCloser(&buf)
}
func main() {}
``` expandable
## Plugin compiler
Tyk provides a Plugin Compiler tool that will create a file that can be [loaded into Tyk](/api-management/plugins/golang#loading-custom-go-plugins-into-tyk) to implement your desired custom logic.
<Note>
The plugin compiler is not supported on Ubuntu 16.04 (Xenial Xerus) as it uses glibc 2.23 which is incompatible with our standard build environment. If you absolutely must have Go plugin support on Xenial, please contact Tyk support.
<ButtonLeft href="https://tyk.io/contact/" color="green" content="Contact us" />
</Note>
### Compiler options
Most of the following arguments are applied only to developer flows. These aid development and testing purposes, and support of these varies across releases, due to changes in the Go ecosystem.
The latest plugin compiler implements the following options:
- `plugin_name`: output root file name (for example `plugin.so`)
- `build_id`: [optional] provides build uniqueness
- `GOOS`: [optional] override of GOOS (add `-e GOOS=linux`)
- `GOARCH`: [optional] override of GOARCH (add `-e GOARCH=amd64`)
By default, if `build_id` is not provided, the gateway will not allow the plugin to be loaded twice. This is a restriction of the Go plugins standard library implementation. As long as the builds are made with a unique `build_id`, the same plugin can be loaded multiple times.
When you provide a unique `build_id` argument, that also enables hot-reload compatibility of your `.so` plugin build, so that you would not need to restart the gateway, only reload it.
- before 5.1: the plugin would be built in a filesystem path based on `build_id`
- since 5.2.4: the plugin compiler adjusts the Go module in use for the plugin.
As the plugins are built with `-trimpath`, to omit local filesystem path details and improve plugin compatibility, the plugin compiler relies on the Go module itself to ensure each plugin build is unique. It modifies the plugin build `go.mod` file and imports to ensure a unique build.
- [plugin package: Warnings](https://pkg.go.dev/plugin#hdr-Warnings)
- [golang#29525 - plugin: cannot open the same plugin with different names](https://github.com/golang/go/issues/29525)
### Output filename
Since v4.1.0 the plugin compiler has automatically added the following suffixes to the root filename provided in the `plugin_name` argument:
- `{Gw-version}`: the Tyk Gateway version, for example, `v5.3.0`
- `{OS}`: the target operating system, for example `linux`
- `{arch}`: the target CPU architecture, for example, `arm64`
Thus, if `plugin_name` is set to `plugin.so` then given these example values the output file will be: `plugin_v5.3.0_linux_arm64.so`.
This enables you to have one directory with multiple versions of the same plugin targeting different Gateway versions.
#### Cross-compiling for different architectures and operating systems
The Tyk Go Plugin Compiler can generate output for different architectures and operating systems from the one in which the compiler is run (cross-compiling). When you do this, the output filename will be suffixed with the target OS and architecture.
You simply provide the target `GOOS` and `GOARCH` arguments to the plugin compiler, for example:
```yaml
docker run --rm -v `pwd`:/plugin-source \
--platform=linux/amd64 \
tykio/tyk-plugin-compiler:v5.2.1 plugin.so $build_id linux arm64
``` expandable
This command will cross-compile your plugin for a `linux/arm64` architecture. It will produce an output file named `plugin_v5.2.1_linux_arm64.so`.
<Note>
If you are using the plugin compiler on MacOS, the docker run argument `--platform=linux/amd64` is necessary. The plugin compiler is a cross-build environment implemented with `linux/amd64`.
</Note>
### Experimental options
The plugin compiler also supports a set of environment variables being passed:
- `DEBUG=1`: enables debug output from the plugin compiler process.
- `GO_TIDY=1`: runs go mod tidy to resolve possible dependency issues.
- `GO_GET=1`: invokes go get to retrieve the exact Tyk gateway dependency.
These environment options are only available in the latest gateway and plugin compiler versions.
They are unsupported and are provided to aid development and testing workflows.
## Loading Custom Go Plugins into Tyk
For development purposes, we are going to load the plugin from local file storage. For production, you can use [bundles](#loading-a-tyk-golang-plugin-from-a-bundle) to deploy plugins to multiple gateways.
In this example we are using a Tyk Classic API. In the API definition find the `custom_middleware` section and make it look similar to the snippet below. Tyk Dashboard users should use RAW API Editor to access this section.
```json
"custom_middleware": {
"pre": [],
"post_key_auth": [],
"auth_check": {},
"post": [
{
"name": "AddFooBarHeader",
"path": "<path>/plugin.so"
}
],
"driver": "goplugin"
}
``` expandable
Here we have:
- `driver` - Set this to `goplugin` (no value created for this plugin) which says to Tyk that this custom middleware is a Golang native plugin.
- `post` - This is the hook name. We use middleware with hook type `post` because we want this custom middleware to process the request right before it is passed to the upstream target (we will look at other types later).
- `post.name` - is your function name from the Go plugin project.
- `post.path` - is the full or relative (to the Tyk binary) path to the built plugin file (`.so`). Make sure Tyk has read access to this file.
Also, let's set fields `"use_keyless": true` and `"target_url": "http://httpbin.org/"` - for testing purposes. We will test what request arrives to our upstream target and `httpbin.org` is a perfect fit for that.
The API needs to be reloaded after that change (this happens automatically when you save the updated API in the Dashboard).
Now your API with its Golang plugin is ready to process traffic:
```bash
# curl http://localhost:8181/my_api_name/get
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip",
"Foo": "Bar",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"url": "https://httpbin.org/get"
}
``` expandable
We see that the upstream target has received the header `"Foo": "Bar"` which was added by our custom middleware implemented as a native Golang plugin in Tyk.
### Updating the plugin
Loading an updated version of your plugin requires one of the following actions:
- An API reload with a NEW path or file name of your `.so` file with the plugin. You will need to update the API spec section `"custom_middleware"`, specifying a new value for the `"path"` field of the plugin you need to reload.
- Tyk main process reload. This will force a reload of all Golang plugins for all APIs.
If a plugin is loaded as a bundle and you need to update it you will need to update your API spec with a new `.zip` file name in the `"custom_middleware_bundle"` field. Make sure the new `.zip` file is uploaded and available via the bundle HTTP endpoint before you update your API spec.
### Loading a Tyk Golang plugin from a bundle
Currently we have loaded Golang plugins only directly from the file system. However, when you have multiple gateway instances, you need a more dynamic way to load plugins. Tyk offer bundle instrumentation [Plugin Bundles](/api-management/plugins/overview#plugin-bundles). Using the bundle command creates an archive with your plugin, which you can deploy to the HTTP server (or AWS S3) and then your plugins will be fetched and loaded from that HTTP endpoint.
You will need to set in `tyk.conf` these two fields:
- `"enable_bundle_downloader": true` - enables the plugin bundles downloader
- `"bundle_base_url": "http://mybundles:8000/abc"` - specifies the base URL with the HTTP server where you place your bundles with Golang plugins (this endpoint must be reachable by the gateway)
Also, you will need to specify the following field in your API spec:
`"custom_middleware_bundle"` - here you place your filename with the bundle (`.zip` archive) to be fetched from the HTTP endpoint you specified in your `tyk.conf` parameter `"bundle_base_url"`
To load a plugin, your API spec should set this field like so:
```json
"custom_middleware_bundle": "FooBarBundle.zip"