Saturday, April 9, 2022

Adding custom headers filter in WSO2 Micro Gateway

Filters are a set of execution points in the request and response flow that intercept the request before it goes to the backend service and intercepts the response before forwarding it to the client. Filters are applied to all the APIs exposed via Microgateway. Custom filter can be engaged to Microgateway runtime using the Microgateway toolkit. If there is a common functionality required by all the APIs exposed via the Microgateway then a custom filter written in ballerina language can be used for that

Use case:

There are few mandatory headers with maximum character length that need to pass to the API. This filter will not affect if the resource has a "/callback"

Sample Headers: AppId (Length:10),  AppVersion (Length:10), RequestId (Length:36)

Save the below content as header_validation_filter.bal in location <PROJECT_HOME>/extensions folder.

import ballerina/http;

import ballerina/lang.'int;

import ballerina/log;

import ballerina/stringutils;

import ballerina/system;


public type HeaderValidationFilter object {


    public function filterRequest(http:Caller caller, http:Request req, http:FilterContext context) returns boolean {


        log:printInfo("<HeaderValidationFilter> : header validation custom filter enabled");

        // string headersArray = "AppId:10, AppVersion:10, RequestId:36";

        string headersString = system:getEnv("headersArray");

        string[] array = stringutils:split(headersString, ",");

        string resourceName = req.rawPath;

        boolean isCallback = stringutils:contains(resourceName,"callback");

        string? remoteHostName = caller.getRemoteHostName();


        string remoteHost = caller.remoteAddress.host;

        log:printInfo("<HeaderValidationFilter> Resource: " + resourceName);

        log:printInfo("<HeaderValidationFilter> isCallback: " + isCallback.toString());        

        log:printInfo("<HeaderValidationFilter> Client: " + remoteHost+" : "+remoteHostName.toString());

        string xFwrd = req.getHeader("X-FORWARDED-FOR");

        string realIp = req.getHeader("X-Real-Ip");

        log:printInfo("<HeaderValidationFilter> X-FORWARDED-FOR: " + xFwrd);

        log:printInfo("<HeaderValidationFilter> X-realIp: " + realIp);


        if(isCallback){

            return true;

        } 

        foreach string header in array {

            log:printDebug("<HeaderValidationFilter> : validating mandatory header : " + header);

            string[] tempArry = stringutils:split(header, ":");

            string headerKey = tempArry[0].trim();

            int | error headerVal = 'int:fromString(tempArry[1]);


            if (req.hasHeader(headerKey)) {

                string apiHeader = req.getHeader(headerKey);

                if (headerVal is int) {

                    if (apiHeader.length() === 0 || apiHeader.length() > headerVal) {

                        log:printInfo("<HeaderValidationFilter> : header " + headerKey + " validation failed");

                        http:Response res = new;

                        res.statusCode = 400;

                        json payload = {errorCode: "ERR_1000006", errorMessage: "Header validation failure", errorDescription: "Header validation failed for : " + headerKey};

                        res.setJsonPayload(payload);

                        var status = caller->respond(res);

                        return false;

                    }

                }

            } else {

                log:printInfo("<HeaderValidationFilter> : header " + headerKey + " not present");

                http:Response res = new;

                res.statusCode = 400;

                json payload = {errorCode: "ERR_1000001", errorMessage: "Missing mandatory parameters", errorDescription: "One or few mandatory parameters are missing in the request"};

                res.setJsonPayload(payload);

                var status = caller->respond(res);

                return false;

            }

        }

        return true;

    }

    public function filterResponse(http:Response response, http:FilterContext context) returns boolean {

        return true;

    }

};

Define the custom filter in the deployment-config.toml file as below.

[[filters]]

      name = "HeaderValidationFilter"

      position = 2

  • name: Name of the filter object defined in the .bal file. Ex: CustomFiler
  • position: The position of the filter to be engaged. By default, microgateway has the following set of filters. By defining the position it can engage the custom filter in between any default filter chain

Then build the project. Custom filter will be engaged in the runtime