Middleware Scripting Guide
Last updated: 6 minutes read.
Middleware Scripting
Middleware scripting is done in either a pre or post middleware chain context, dynamic middleware can be applied to both session-based APIs and Open (Keyless) APIs.
The difference between the middleware types are:
-
Pre: These middleware instances do not have access to the session object (as it has not been created yet) and therefore cannot perform modification actions on them.
-
Post: These middleware components have access to the session object (the user quota, allowances and auth data), but have the option to disable it, as deserialising it into the JSVM is computationally expensive and can add latency.
Note
A new JSVM instance is created for each API that is managed, this means that inter-API communication is not possible via shared methods (they have different bounds), however it is possible using the session object if a key is shared across APIs.
Declared Plugin Functions
Plugin functions are available globally in the same namespace. So, if you include two or more JSVM plugins that call the same function, the last declared plugin implementation of the function will be returned.
Enable the JSVM
Before you can use Javascript Middleware you will need to enable the JSVM
You can do this by setting enable_jsvm
to true
in your tyk.conf
file.
Creating a middleware component
Tyk injects a TykJS
namespace into the JSVM, this namespace can be used to initialise a new middleware component. Each middleware component should be in its own *.js
file.
Note
The middleware variable name should match the name of the file it’s in.
Creating a middleware object is done my calling the TykJS.TykMiddleware.NewMiddleware({})
constructor with an empty object and then initialising it with your function using the NewProcessRequest()
closure syntax.
Here is an example implementation:
/* --- sampleMiddleware.js --- */
// Create your middleware object
var sampleMiddleware = new TykJS.TykMiddleware.NewMiddleware({});
// Initialise it with your functionality by passing a closure that accepts two objects
// into the NewProcessRequest() function:
sampleMiddleware.NewProcessRequest(function(request, session, spec) {
console.log("This middleware does nothing, but will print this to your terminal.")
// You MUST return both the request and session metadata
return sampleMiddleware.ReturnData(request, session.meta_data);
});
Middleware component variables
As well as the API functions that all JSVM components share, the middleware components have access to some data structures that are performant and allow for the modification of both the request itself and the session. These objects are exposed to the middleware in the form of the request
, session
and spec
objects in the NewProcessRequest(function(request, session) {};
call.
In the example above, we can see that we return 2 of these variables (request
and session
meta data) - this is a requirement, and omitting it can cause the middleware to fail, this line should be called at the end of each process:
return sampleMiddleware.ReturnData(request, session.meta_data);
This allows the middleware machinery to perform the necessary writes and changes to the two main context objects.
The request
object
The request
object provides a set of arrays that can be manipulated, that when changed, will affect the request as it passes through the middleware pipeline, the request
object looks like this:
{
Headers map[string][]string
SetHeaders map[string]string
DeleteHeaders []string
Body string
URL string
AddParams map[string]string
DeleteParams []string
ReturnOverrides {
ResponseCode: int
ResponseError: string
ResponseBody: string
ResponseHeaders []string
}
IgnoreBody bool
Method string
RequestURI string
Scheme string
}
Note
From v2.9.3, ResponseError
has been deprecated. You should use ResponseBody
instead.
Headers
: This is an object of string arrays, and represents the current state of the request header. This object cannot be modified directly, but can be used to read header data.SetHeaders
: This is a key-value map that will be set in the header when the middleware returns the object, existing headers will be overwritten and new headers will be added.DeleteHeaders
: Any header name that is in this list will be deleted from the outgoing request.DeleteHeaders
happens beforeSetHeaders
.Body
: This represents the body of the request, if you modify this field it will overwrite the request.URL
: This represents the path portion of the outbound URL, use this to redirect a URL to a different endpoint upstream.AddParams
: You can add parameters to your request here, for example internal data headers that are only relevant to your network setup.DeleteParams
: These parameters will be removed from the request as they pass through the middleware.DeleteParams
happens beforeAddParams
.ReturnOverrides
: Values stored here are used to stop or halt middleware execution and return an error code if the middleware operation has failed. You can also set theResponseHeader
for the response.IgnoreBody
: If this parameter is set to true, the original request body will be used. If set to false theBody
field will be used, this is the default behavior.Method
: Contains the HTTP method (GET
,POST
, etc.).RequestURI
: Contains the request URI, including the query string, e.g./path?key=value
.Scheme
: Contains the URL scheme, e.g.http
,https
.
JSVM Example
var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({});
testJSVMData.NewProcessRequest(function(request, session, config) {
request.ReturnOverrides.ResponseError = "Foobarbaz"
request.ReturnOverrides.ResponseBody = "Foobar"
request.ReturnOverrides.ResponseCode = 200
request.ReturnOverrides.ResponseHeaders = {
"X-Foo": "Bar",
"X-Baz": "Qux"
}
return testJSVMData.ReturnData(request, {});
});
Note
Fom v2.9.3 you should use ResponseBody
. ResponseError
has been deprecated.
Using the methods outlined above, alongside the API functions that are made available to the VM, allows for a powerful set of tools for shaping and structuring inbound traffic to your API, as well as processing, validating or re-structuring the data as it is inbound.
The session
object
Tyk uses an internal session representation to handle the quota, rate limits, and access allowances of a specific key. This data can be made available to POST-processing middleware for processing. the session object itself cannot be edited, as it is crucial to the correct functioning of Tyk.
In order for middleware to be able to transfer data between each other, the session object makes available a meta_data
key/value field that is written back to the session store (and can be retrieved by the middleware down the line) - this data is permanent, and can also be retrieved by the REST API from outside of Tyk using the /tyk/keys/
method.
The session object has the same representation as the one used by the API:
{
"allowance": 999,
"rate": 1000,
"per": 60,
"expires": 0,
"quota_max": -1,
"quota_renews": 1406121006,
"quota_remaining": 0,
"quota_renewal_rate": 60,
"access_rights": {
"234a71b4c2274e5a57610fe48cdedf40": {
"api_name": "Versioned API",
"api_id": "234a71b4c2274e5a57610fe48cdedf40",
"versions": [
"v1"
]
}
},
"org_id": "53ac07777cbb8c2d53000002",
"meta_data": {
"your-key": "your-value"
}
}
There are other ways of accessing and editing a session object by using the Tyk JSVM API functions.
Passing Custom Attributes to Middleware
You can use the config_data
special field in your API definition to pass custom attributes to middleware via the JSVM.
Adding config_data
to an API Definition
Add the following to the root of your API definition:
"config_data": {
"foo": "bar"
},
Sample use of config_data
var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({});
testJSVMData.NewProcessRequest(function(request, session, spec) {
request.SetHeaders["data-foo"] = spec.config_data.foo;
return testJSVMData.ReturnData(request, {});
});