plugin

package
v6.0.2+incompatible Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 22, 2021 License: Apache-2.0, BSD-2-Clause, BSD-3-Clause, + 1 more Imports: 24 Imported by: 0

README

Adding a Plugin

To add a plugin, create a new .go file in the grove/plugin directory. This file should have a unique name, to avoid conflicts. Consider prefixing it with your company name, website, or a UUID.

The filename, sans .go, is the name of your plugin, and will be the key used for configuration in the remap file. For example, if your file is f49e54fc-fd17-4e1c-92c6-67028fde8504-hello-world.go, the name of your plugin is f49e54fc-fd17-4e1c-92c6-67028fde8504-hello-world.

Plugins are registered via calls to AddPlugin inside an init function in the plugin's file.

The Funcs object contains functions for each hook, as well as a load function for loading configuration from the remap file. The current hooks are startup, onRequest, beforeParentRequest, beforeRespond, and afterRespond. If your plugin does not use a hook, it may be nil.

  • startup is called when the application starts. Examples are set global data, or start a global goroutine needed by the plugin.

  • onRequest is called immediately when a request is received. It returns a boolean indicating whether to stop processing. Examples are IP blocking, or serving custom endpoints for statistics or to invalidate a cache entry.

  • beforeCacheLookUp is called immedidiately before looking the object up in the cache. It can be used to modify the cacheKey to be used to for this object using the passed CacheKeyOverrideFunc func. Once set using that function Grove will keep using that cacheKey throughout the life of the object in the cache.

  • beforeParentRequest is called immediately before making a request to a parent. It may manipulate the request being made to the parent. Examples are removing headers in the client request such as Range.

  • beforeRespond is called immediately before responding to a client. It may manipulate the code, headers, and body being returned. Examples are header modifications, or handling if-modified-since requests.

  • afterRespond is called immediately after responding to the client. Examples are recording stats, or writing to an access log.

  • load is not a hook, but rather a function to load arbitrary data from the remap config file. It is given a json.RawMessage, and can return any object. The object it returns is then passed to this plugin's hooks.

The simplest example is the hello_world plugin. See grove/plugin/hello_world.go.

func init() {
	AddPlugin(10000, Funcs{startup: hello})
}

func hello(icfg interface{}, d StartupData) {
	log.Errorf("Hello World! I'm a startup plugin! We're starting with %v bytes!\n", d.Config.CacheSizeBytes)
}

The plugin is initialized via AddPlugin, and its hello function is set as the startup hook. The hello function has the signature of plugin.StartupHook.

To pass data from one hook in your plugin to a hook that's called later, set the Context member of the Data object. For an example, see hello_context.go:

func init() {
	AddPlugin(10000, Funcs{startup: helloCtxStart, afterRespond: helloCtxAfterResp})
}

func helloCtxStart(icfg interface{}, d StartupData) {
	*d.Context = 42
	log.Debugf("Hello World! Start set context: %+v\n", d.Context)
}

func helloCtxAfterResp(icfg interface{}, d AfterRespondData) {
	ictx := d.Context
	ctx, ok := (*ictx).(int)
	log.Debugf("Hello World! After Response got context: %+v %+v\n", ok, ctx)
}

The startup hook function helloCtxStartup sets the context pointer to the value 42, and then the same plugin's afterRespond hook helloCtxAfterResp retrieves the value from its Data .Context pointer.

For an example of configuration, see modify_headers.go.

func init() {
	AddPlugin(10000, Funcs{load: modRespHdrLoad, beforeRespond: modRespHdr})
}

type Hdr struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type ModHdrs struct {
	Set  []Hdr    `json:"set"`
	Drop []string `json:"drop"`
}

func modRespHdrLoad(b json.RawMessage) interface{} {
	cfg := ModHdrs{}
	err := json.Unmarshal(b, &cfg)
	if err != nil {
		log.Errorln("modifyheaders loading config, unmarshalling JSON: " + err.Error())
		return nil
	}
	log.Debugf("modifyheaders load success: %+v\n", cfg)
	return &cfg
}

func modRespHdr(icfg interface{}, d BeforeRespondData) {
	if icfg == nil {
		log.Debugln("modifyheaders has no config, returning.")
		return
	}
	cfg, ok := icfg.(*ModHdrs)
	if !ok {
		// should never happen
		log.Errorf("modifyheaders config '%v' type '%T' expected *ModHdrs\n", icfg, icfg)
		return
	}
...
}

The load function modRespHdrLoad unmarshals the given json.RawMessage into its expected type, and returns a pointer to the object. The beforeRespond hook function modRespHdr then casts the interface it's given to the type its load function returned.

Load functions are given configuration objects from the remap file, for the rule being processed, under the key plugins. Recall the name of the plugin is the filename, with .go removed. For example, for the modify_headers.go plugin:

{
  "rules": [
    {
      "name": "my-remap-rule",
      "plugins": {
        "modify_headers": {
          "set": [{"name": "Server", "value": "grove42"}],
          "drop": ["X-Powered-By"]
        }
      },
  ...

If a given rule does not have a plugin key, the global object will be used, as with other remap fields. For example:

{
  "plugins": {
    "modify_headers": {
    "set": [{"name": "Server", "value": "grove42"}],
    "drop": ["X-Powered-By"]
  },
  "rules": [
  ...

Documentation

Index

Constants

View Source
const CacheStatsEndpoint = "/_cacheinspect"

CacheStatsEndpoint is our reserved path

View Source
const CallGCEndpoint = "/_callgc"
View Source
const MAXINT64 = 1<<63 - 1
View Source
const MemStatsEndpoint = "/_memstats"
View Source
const NSPerSec = 1000000000
View Source
const StatsEndpoint = "/_astats"

Variables

This section is empty.

Functions

func AddPlugin

func AddPlugin(priority uint64, funcs Funcs)

func LoadRemapStats

func LoadRemapStats(stats stat.Stats, httpConns *web.ConnMap, httpsConns *web.ConnMap) map[string]interface{}

func LoadSystemStats

func LoadSystemStats(stats stat.Stats, interfaceName string) stat.StatsSystemJSON

Types

type AfterRespondData

type AfterRespondData struct {
	W         http.ResponseWriter
	Stats     stat.Stats
	RequestID uint64
	cachedata.ReqData
	cachedata.SrvrData
	cachedata.ParentRespData
	cachedata.RespData
	Context *interface{}
}

type AfterRespondFunc

type AfterRespondFunc func(icfg interface{}, d AfterRespondData)

type BeforeCacheLookUpData

type BeforeCacheLookUpData struct {
	Req                  *http.Request
	CacheKeyOverrideFunc func(string)
	DefaultCacheKey      string
	Context              *interface{}
}

type BeforeCacheLookupFunc

type BeforeCacheLookupFunc func(icfg interface{}, d BeforeCacheLookUpData)

type BeforeParentRequestData

type BeforeParentRequestData struct {
	Req       *http.Request
	RemapRule string
	Context   *interface{}
}

type BeforeParentRequestFunc

type BeforeParentRequestFunc func(icfg interface{}, d BeforeParentRequestData)

type BeforeRespondData

type BeforeRespondData struct {
	Req *http.Request
	// CacheObj is the object to be cached, containing information about the origin request. The code, headers, and body should not be considered authoritative. Look at Code, Hdr, and Body instead, as the actual values about to be sent. Note CacheObj may be nil, if an error occurred (e.g. the Origin failed to respond).
	CacheObj  *cacheobj.CacheObj
	Code      *int
	Hdr       *http.Header
	Body      *[]byte
	RemapRule string
	Context   *interface{}
}

BeforeRespondData holds the data passed to plugins. The objects pointed to MAY NOT be modified, however, the location pointed to may be changed for the Code, Hdr, and Body. That iss, `*d.Hdr = myHdr` is ok, but `d.Hdr.Add("a", "b") is not. If that's confusing, recall `http.Header` is a map, therefore Hdr and Body are both pointers-to-pointers.

type BeforeRespondFunc

type BeforeRespondFunc func(icfg interface{}, d BeforeRespondData)

type Funcs

type Funcs struct {
	// contains filtered or unexported fields
}

type LoadFunc

type LoadFunc func(json.RawMessage) interface{}

type OnRequestData

type OnRequestData struct {
	W             http.ResponseWriter
	R             *http.Request
	InterfaceName string
	Stats         stat.Stats
	StatRules     remapdata.RemapRulesStats
	HTTPConns     *web.ConnMap
	HTTPSConns    *web.ConnMap
	RequestID     uint64
	Context       *interface{}
	cachedata.SrvrData
}

type OnRequestFunc

type OnRequestFunc func(icfg interface{}, d OnRequestData) bool

type Plugins

type Plugins interface {
	LoadFuncs() map[string]LoadFunc
	OnStartup(cfgs map[string]interface{}, context map[string]*interface{}, d StartupData)
	OnRequest(cfgs map[string]interface{}, context map[string]*interface{}, d OnRequestData) bool
	OnBeforeCacheLookup(cfgs map[string]interface{}, context map[string]*interface{}, d BeforeCacheLookUpData)
	OnBeforeParentRequest(cfgs map[string]interface{}, context map[string]*interface{}, d BeforeParentRequestData)
	OnBeforeRespond(cfgs map[string]interface{}, context map[string]*interface{}, d BeforeRespondData)
	OnAfterRespond(cfgs map[string]interface{}, context map[string]*interface{}, d AfterRespondData)
}

func Get

func Get(enabled []string) Plugins

type StartupData

type StartupData struct {
	Config  config.Config
	Context *interface{}
	// Shared is the "plugins_shared" data for all rules. This is a `map[ruleName][key]value`. Keys and values are arbitrary data. This allows plugins to do pre-processing on the config, and store computed data in the context, to save processing during requests.
	Shared map[string]map[string]json.RawMessage
}

type StartupFunc

type StartupFunc func(icfg interface{}, d StartupData)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL