Configuration and headers¶
We'll learn how to add additional headers to HTTP responses in this lab. We'll use the main.go
file created in the previous lab.
Adding HTTP response header¶
We'll create the OnHttpResponseHeaders
function, and within the function, we'll add a new response header using the AddHttpResponseHeader
from the SDK.
Create the OnHttpResponseHeaders
function that looks like this:
func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
proxywasm.LogInfo("OnHttpResponseHeaders")
err := proxywasm.AddHttpResponseHeader("my-new-header", "some-value-here")
if err != nil {
proxywasm.LogCriticalf("failed to add response header: %v", err)
}
return types.ActionContinue
}
Let's rebuild the extension:
And we can now re-run the Envoy proxy with the updated extension:
Now, if we send a request again (make sure to add the -v
flag), we'll see the header that got added to the response:
...
< HTTP/1.1 200 OK
< content-length: 13
< content-type: text/plain
< my-new-header: some-value-here
< date: Mon, 22 Jun 2021 17:02:31 GMT
< server: envoy
<
hello world
You can stop running Envoy by typing fg
and pressing Ctrl+C.
Reading values from configuration¶
Hard-coded values in code are never a good idea. Let's see how we could read the additional headers from the configuration we provided in the Envoy configuration file.
Let's start by adding the plumbing that will allow us to read the headers from the configuration.
-
Add the
additionalHeaders
andcontextID
to thepluginContext
struct:
Note
The additionalHeaders
variables is a map of strings that will store header keys and values we'll read from the configuration.
-
Update the
NewPluginContext
function to return the plugin context with initialized variables:
As for reading the configuration - we have two options - we can read the configuration set at the plugin level using the GetPluginConfiguration
function or at the VM level using the GetVMConfiguration
function.
Typically, you'd read the configuration when the plugin starts (OnPluginStart
) or when the VM starts (OnVMStart
).
Parsing JSON from config file¶
Let's add the OnPluginStart
function where we read in values from the Envoy configuration and store the key/value pairs in the additionalHeaders
map. We'll use the fastjson library (github.com/valyala/fastjson
) to parse the JSON string:
func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
data, err := proxywasm.GetPluginConfiguration() // (1)
if err != nil {
proxywasm.LogCriticalf("error reading plugin configuration: %v", err)
}
var p fastjson.Parser
v, err := p.ParseBytes(data)
if err != nil {
proxywasm.LogCriticalf("error parsing configuration: %v", err)
}
obj, err := v.Object()
if err != nil {
proxywasm.LogCriticalf("error getting object from json value: %v", err)
}
obj.Visit(func(k []byte, v *fastjson.Value) {
ctx.additionalHeaders[string(k)] = string(v.GetStringBytes()) // (2)
})
return types.OnPluginStartStatusOK
}
- We use the
GetPluginConfiguration
function to read the configuration section from the Envoy config file. - We iterate through all key/value pairs from the configuration and store them in the
additionalHeaders
map.
Note
Make sure to add the github.com/valyala/fastjson
to the import statements at the top of the file and run go mod tidy
to download the dependency.
To access the configuration values we've set, we need to add the map to the HTTP context when we initialize it. To do that, we need to update the httpContext
struct first and add the additionalHeaders
map:
type httpContext struct {
// Embed the default http context here,
// so that we don't need to reimplement all the methods.
types.DefaultHttpContext
contextID uint32
additionalHeaders map[string]string
}
Then, in the NewHttpContext
function we can instantiate the httpContext
with the additionalHeaders
map coming from the pluginContext
:
func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
return &httpContext{contextID: contextID, additionalHeaders: ctx.additionalHeaders}
}
Calling AddHttpResponseHeader
¶
Finally, in order to set the headers we modify the OnHttpResponseHeaders
function, iterate through the additionalHeaders
map and call the AddHttpResponseHeader
for each item:
func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
proxywasm.LogInfo("OnHttpResponseHeaders")
for key, value := range ctx.additionalHeaders { // (1)
if err := proxywasm.AddHttpResponseHeader(key, value); err != nil { // (2)
proxywasm.LogCriticalf("failed to add header: %v", err)
return types.ActionPause
}
proxywasm.LogInfof("header set: %s=%s", key, value) // (3)
}
return types.ActionContinue
}
-
Iterate through the
additionalHeaders
map created in theNewPluginContext
. The map contains the data from the config file we parsed in theOnPluginStart
function -
We call the
AddHttpResponseHeader
to set the response headers - We also log out the header key and value
Complete main.go file
Let's rebuild the extension again:
We also have to update the config file to include additional headers in the filter configuration (the configuration
field):
- name: envoy.filters.http.wasm
typed_config:
'@type': type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
vm_config:
runtime: 'envoy.wasm.runtime.v8'
code:
local:
filename: 'main.wasm'
configuration:
'@type': type.googleapis.com/google.protobuf.StringValue
value: |
{
"header_1": "somevalue",
"header_2": "secondvalue"
}
Complete envoy.yaml file
We can re-run the Envoy proxy with the updated configuration using func-e run -c envoy.yaml &
.
This time, when we send a request, we'll notice the headers we set in the extension configuration is added to the response:
...
< HTTP/1.1 200 OK
< content-length: 13
< content-type: text/plain
< header_1: somevalue
< header_2: secondvalue
< date: Mon, 22 Jun 2021 17:54:53 GMT
< server: envoy
...
You can stop running Envoy by bringing it to the foreground using fg
and pressing Ctrl+C.