Creating CloudEDGE Handler Files
CloudEDGE workers provide a way to augment your application by modifying the requests that it handles and the responses that it generates without the need to change your existing, deployed application code.
Handlers run your custom JavaScript code in a serverless execution environment that is agnostic to your cloud provider. They provide a uniform development environment and deployment flexibility. Additionally, handlers can run at the CDN edge, closer to your users, which allows for improved response times.
For more information about CloudEDGE Workers, see CloudEDGE Workers Overview.
Note:
Currently, Webscale supports Amazon EC2 (also known as Amazon Elastic Compute Cloud).Topics
- Handler files
- Writing handler functions
- CloudEDGE handler interfaces
- wRequest and wResponse objects
- AWS property mutability
- Examples of handler functions
- Testing handlers
- Integrating the CloudEDGE handler testing framework
- Enabling the CloudEDGE Workers feature
Handler files
A CloudEDGE handler is implemented in a handler file. The handler file is structured as a NPM package. It includes your JavaScript source code and all of the third party-dependencies (also known as node modules) needed to run your code. You do not need to publish this package to the official NPM repository.
In addition to the source code and node modules, the handler file must include two files: package.json
and manifest.json
. These files must exist in the same directory, which is the package’s root directory.
The package.json
file describes the dependencies of the source code, the most important of which is the @webscale-networks/cloudedge-handlers
node module. This Webscale node module runs your handler in a serverless execution environment once it is deployed. The environment is independent of your cloud provider. The Webscale API will reject handler files that fail to include this dependency in both the dependencies
and bundledDependencies
sections of their package.json
file. Additionally, the @webscale-networks/cloudedge-handlers
node module must exist at <root-directory>/node_modules
.
The manifest.json
file contains metadata about the source code of your handler file. Specifically, it enumerates the handler functions contained within, where their implementations exist, and whether they process requests or responses.
manifest.json file specification
[{
'moduleName': a unique module name as a string,
'relativePath':
relative path from package root directory to the handler function source file,
'functions': [{
name: function name as a string,
type: one of ‘handleRequest’ or 'handleResponse',
}]
}]
Example of a manifest.json file
The following sample code is for a single manifest.json file with two handler functions.
[{
'moduleName': handlerSource,
'relativePath': “src/handler.js”,
'functions': [
{
name: “addsRequestHeader”,
type: “handleRequest”,
},
{
name: “hijacksResponseBody”,
type: “handleResponse”,
},
]
}]
Writing handler functions
Handlers that process requests are called request handlers. Likewise, handlers that process responses are called response handlers. When creating the implementation of a handler function, it is important to decide whether it should process requests or responses as the interfaces are similar but not identical.
To reiterate, a single source file can contain multiple handler functions, mixing request and response handlers. However, a single handler function cannot process both requests and responses. The function that runs depends on the handler function that an end user chooses when they create a handler through the Webscale Control Panel. For general information on working with handlers through the Webscale Control Panel, see CloudEDGE Workers Overview.
CloudEDGE handler interfaces
Depending on what type of handler function you write, the function must accept different arguments.
- Request handlers must accept a single argument, representing the client’s request.
- Response handlers must accept two arguments. The first one is the client’s request and the second is the generated response.
Handler functions are not forced to return a result. One example is if the function decides not to modify the wRequest
or wResponse
object. If the handler function does return a result, it must be an object containing a subset of the following properties: error
, request
, and response
. The error
property, if set, should be a JavaScript error.
Request handlers
Request handlers can set the following properties on their returned object.
- Return a JavaScript error by setting the
error
property on the returned object. The error will be logged in the cloud provider. The cloud provider’s default error response will be returned to the client. - Return a JavaScript error and custom response by setting the
error
andresponse
properties. The error will be logged in the cloud provider. The response will be returned to the client. - Return a custom response by setting the
response
property - Update the request by setting the
request
property - Return nothing
Response handlers
Response handlers can set the following properties on their returned object.
- Return a JavaScript error by setting the
error
property on the returned object. The error will be logged in the cloud provider. The cloud provider’s default error response will be returned to the client. - Return a JavaScript error and custom response by setting the
error
andresponse
properties - Return a custom or modified response by setting the
response
property - Return nothing
Response handlers cannot return an object with the request
property set.
wRequest and wResponse objects
If your handler function processes requests, it will be called with a wRequest
object.
If your handler function processes responses, it will be called with a wRequest
and wResponse
object.
wRequest object
{
body: {
data: ...,
encoding: ...,
modified: ...,
truncated: ...,
},
headers: ...,
method: ...,
path: ...,
peerAddress: ...,
query: ...,
originRequest: {
headers: ...,
host: ...,
keepaliveTimeout: ...,
path: ...,
port: ...,
protocol: ...,
readTimeout: ...,
sslProtocols: ...,
},
providerSpecific: {
...,
}
}
wRequest object properties
Property | Description |
---|---|
body.data |
Content of the request body |
body.encoding |
Encoding of the request body. Valid values are text or base64 . |
body.modified |
If your handler modifies the original request body, this property must be set to true |
body.truncated |
Some cloud providers place limits on the size of request bodies. If a request body is larger than this limit, they might truncate the content that is available to your handler. Valid values are true or false . |
headers |
HTTP request headers that the client sent. A header value can be retrieved by calling its get method with the header name. A header value can be set by calling its set method with the header name and value. Header names are case-insensitive. |
method |
HTTP method of the client’s request |
originRequest.headers |
HTTP request headers that will be sent to origin. A header value can be retrieved by calling its get method with the header name. A header value can be set by calling its set method with the header name and value. Header names are case-insensitive. |
originRequest.host |
Hostname used on the request sent to origin |
originRequest.keepaliveTimeout |
Maximum amount of time that the connection to origin will be kept alive (in seconds) |
originRequest.path |
Path to use when retrieving a resource from origin |
originRequest.port |
Port number on which to connect to origin |
originRequest.protocol |
Connection protocol to use when connecting to origin |
originRequest.readTimeouts |
Maximum amount of time to wait for a response from origin (in seconds) |
originRequest.sslProtocols |
Minimum SSL/TLS protocol to use when creating an HTTPS connection with origin, as an array. If multiple protocols are specified, they are tried in order. |
path |
Path from the requested URI. This property can be changed so the HTTP response returns a different resource. |
peerAddress |
IP address of the client from which the request originates |
providerSpecific |
Object containing any additional information included by your cloud provider |
query |
Query string from the requested URI |
wResponse object
{
body: {},
headers: ...,
statusCode: ...,
providerSpecific: {
...,
}
}
wResponse object properties
Property | Description |
---|---|
body |
Not all cloud providers grant access to the response body. If your cloud provider does, this property stores an object with the data and encoding properties. These properties can be updated if they exist and added if they do not. Using the body property, you can inspect or replace the response. The encoding of data must match the value of the encoding property. |
headers |
HTTP response headers that will be sent to the client. A header value can be retrieved by calling its get method with the header name. A header value can be set by calling its set method with the header name and value. Header names are case-insensitive. |
providerSpecific |
An object containing any additional information included by your cloud provider. The properties that are mutable depend on your cloud provider. |
statusCode |
HTTP status code sent to the client. This property can be updated and it must be specified. |
AWS property mutability
wRequest
Immutable:
peerAddress
method
- Properties in
providerSpecific
Mutable:
- All other fields
wResponse
Immutable:
- Properties in
providerSpecific
Mutable:
- All other fields
Examples of handler functions
Following are some examples of handler functions.
Request handlers
Generating an error when a specific request header is set
exports.generatesErrorHandler = (wRequest) => {
if (wRequest.headers.get('webscale-generate-error')) {
return {
error: Error('Operator error'),
response: {
body: {
modified: true,
data: '<html>Generated error</html>',
encoding: 'text',
},
headers.set('content-type', 'text/html'),
statusCode: 400,
},
};
}
};
Modifying the request query parameters
exports.addQueryHandler = (wRequest) => {
wRequest.query = "delay=10s";
return {
request: wRequest,
};
};
Adding a request header sent to origin using an asynchronous JavaScript function
exports.addCustomOriginHeader = async (wRequest) => {
wRequest.originRequest.headers.set(
'your-header-name',
'your-header-value',
);
return new Promise((resolve, reject) => {
resolve({
request: wRequest,
});
});
};
Modifying the request body sent to origin using an asynchronous JavaScript function
exports.changeRequestBodyHandlerAsync = async (wRequest) => {
if (wRequest.method == 'POST') {
wRequest.headers.set('host', 'ptsv2.com');
wRequest.originRequest.host = 'ptsv2.com';
wRequest.originRequest.protocol = 'http';
wRequest.originRequest.port = 80;
wRequest.body.modified = true;
wRequest.body.encoding = 'text';
wRequest.body.data = 'This is a brand new POST body.';
}
return new Promise((resolve, reject) => {
resolve({
request: wRequest,
});
});
};
Response handlers
Redirecting a response to another page
exports.redirectToWebscale = (wRequest, wResponse) => {
wResponse.statusCode = 301;
wResponse.headers.set(
'location',
'https://www.webscale.com',
);
return {
response: wResponse,
};
};
Replacing a response completely
exports.helloWorldResponse = (wRequest, wResponse) => {
wResponse.body.modified = true;
wResponse.body.data = '<html>hello world</html>';
wResponse.body.encoding = 'text';
wResponse.headers.set('content-type', 'text/html');
wResponse.headers.set('content-encoding', '');
return {
response: wResponse,
};
};
Hijacking the response to return an error using an asynchronous JavaScript function
exports.responseError = async (wRequest, wResponse) => {
wResponse.body.modified = true;
wResponse.body.encoding = 'text';
wResponse.body.data = '<html>Resource not found</html>';
wResponse.headers.set('content-type', 'text/html');
wResponse.headers.set('content-encoding', '');
wResponse.statusCode = 404;
return new Promise((resolve, reject) => {
resolve({
error: Error('Not found'),
response: wResponse,
});
});
};
Testing handlers
You can write unit tests for your handler using any JavaScript testing framework. Additionally, the @webscale-networks/cloudedge-handlers
node module includes a testing framework that will automatically run each handler function defined in your manifest.json
file against a collection of tests that enforce the CloudEDGE handler interfaces and specific constraints of your cloud provider.
To run the CloudEDGE handler test suite, simply run the run-execution-testing.js
node script. To integrate the test suite into your NPM build process, you can run the script on the posttest
NPM event by updating the scripts section of your package.json
file.
Note:
NPM versions 8.0.0 and higher require atest
event as well. If you do not have tests of your own, input an empty string.
Integrating the CloudEDGE handler testing framework
“scripts”: {
...
“posttest“:
"node node_modules/@webscale-networks/cloudedge-handlers/run-execution-testing.js <cloud-provider>",
...
}
<cloud-provider>
should be replaced with the name of the cloud provider for the test. For AWS providers, use aws
.
To execute NPM’s testing pipeline, run npm test
in the root directory of your package.
To create mock wRequest
and wResponse
objects in your own unit tests, import the following mock data available to you from the CloudEDGE handler test suite.
- Complete
wRequest
andwResponse
objects are available in:.../node_modules/@webscale-networks/cloudedge-handlers/execution_testing/webscale/src/object.js
- The case-insensitive HTTP header class is available in:
.../node_modules/@webscale-networks/cloudedge-handlers/execution_testing/utils/src/headers.js
Further reading
Have questions not answered here? Please Contact Support to get more help.
Feedback
Was this page helpful?
Glad to hear it! Have any more feedback? Please share it here.
Sorry to hear that. Have any more feedback? Please share it here.