Creating a connection handler

This topic describes how to write a connection handler that can be configured to process requests and responses for a connection destination.

Overview

A connection handler is a small piece of logic that can be configured to examine and process the requests sent to, and the responses received from, a connection destination. For more information about destination configuration and an overview of destination processing, see Configuring destinations. For an overview of connection handlers and their configuration, see Configuring connection handlers.

There are two basic approaches that can be used to write a connection handler:

  • Implement the connection handler by writing two event handlers; one to process the connection request and one to process the connection response. An event-based connection handler can be implemented by a Groovy or PHP script, or a Java™ class.
  • Implement the connection handler by writing a Java class that implements the zero.core.connection.handlers.Handler interface.

If you are writing a new connection handler you might choose the simplicity of the event-based approach but older connection handler implementations might still be implementing the zero.core.connection.handlers.Handler interface directly. This topic focuses on the use of the simpler, event-based approach but the techniques and global contexts keys discussed also apply to the class-based approach.

Writing an event-based connection handler

An event-based connection handler is implemented by handling two events; one to process the connection request and one to process the connection response. The event names are constructed from the connection handler name followed by Request or Response respectively. For example, if the connection handler name is myConnectionHandler then the request event is myConnectionHandlerRequest and the response event is myConnectionHandlerResponse.

The following Groovy script contains an example implementation of a connection handler called myConnectionHandler:

def onMyConnectionHandlerRequest() {
    // Request processing logic
}

def onMyConnectionHandlerResponse() {
    // Response processing logic
}

When you write an event-based connection handler, you must also configure the request and response event handler implementations to handle the corresponding events. For example, if the myConnectionHandler implementation is saved as myConnectionHandler.groovy in the application app/scripts directory, then the following configuration makes the connection handler available for use:

/config/handlers += [{
    "events" : ["myConnectionHandlerRequest", "myConnectionHandlerResponse"],
    "handler" : "myConnectionHandler.groovy" 
}]

After the connection handler had been implemented and registered with the corresponding events, it can be configured for one or more connection destinations. For example, the following configuration associates the myConnectionHandler connection handler with a connection destination:

/config/connection/destinations += {
    "http://www.projectzero.org/*" : {
        "handlers" : [{
            "name" : "myConnectionHandler"
        }]
    }
}

When a connection request is sent to this destination, the myConnectionHandlerRequest event will be fired and the connection handler can inspect or modify the request before it is sent to the target service. When the response is retrieved, the myConnectionHandlerResponse event will be fired and the connection handler can inspect of modify the response before it is returned to the application.

For more information about destination configuration and an overview of destination processing, see Configuring destinations. For an overview of connection handlers and their configuration, see Configuring connection handlers.

The connection context

The request and response handler implementations interact directly with the /connection zone of the global context. The contents of the outbound request can be accessed using the following keys:

/connection/request/target
Destination resource name.
/connection/request/operation
Operation name.
/connection/request/headers/ header_name
List of values for request header.
/connection/request/cookies
List of Cookie values for request, if applicable.
/connection/request/protocol/_name
Name of the protocol requested by the application.
/connection/request/protocol/ parameter_name
Protocol configuration value.
/connection/request/body
Request body. The object type of the request body depends on the body supplied by the caller of the Connection API.

The contents of the response can be accessed using the following keys:

/connection/response/headers/ header_name
List of values for response header.
/connection/response/cookies
List of Cookie values for response, if applicable.
/connection/response/status
Response status String.
/connection/response/body
Response body. The response body can be any object type, including InputStream and Reader. Exception conditions are indicated by the response body being an instance of Exception.

The value of a header stored under /connection/request/headers/ header_name and /connection/response/headers/ header_name is always a list. If a connection handler implementation attempts to set a header value that is not an instance of List, it is automatically placed in a list before it is stored in the /connection zone.

Reading connection handler configuration

If a handler requires configuration, it can be specified when a handler is registered with a connection destination, as described in Configuring connection handlers. When the request and response handler implementations are invoked, the configuration is made available in the global context as a Map, under the key /connection/configuration.

In the following example configuration, the connection handler, myConnectionHandler, is configured for the named destination and supplies some handler configuration:

/config/connection/destinations += {
    "http://www.projectzero.org/*" : {
        "handlers" : [{
            "name" : "myConnectionHandler",
            "config" : {
                "myConfig" : "myValue"
            }
        }]
    }
}

When the myConnectionHandler request and response handler implementations are invoked for this destination, the value of /connection/configuration#myConfig will be myValue. In the following example Groovy script, the value of the myConfig configuration parameter is retrieved in both the request and response handler implementations:

def onMyConnectionHandlerRequest() {
    def value = zget("/connection/configuration#myConfig");
    // value will contain "myValue"
}

def onMyConnectionHandlerResponse() {
    def value = zget("/connection/configuration#myConfig");
    // value will contain "myValue"
}
  • A connection handler must not replace or modify the configuration map at /connection/configuration. Attempting to do this results in a runtime exception.
  • For more information about destination configuration, see Configuring destinations.

Carrying state between request and response event handlers

An event-based connection handler can carry state between the request and response event handlers using the /event zone. The request event handler can place any state it needs to pass to the response handler in the /event zone. When the response event is fired, this state is restored to the /event zone, from where the response event handler can retrieve it.

In the following example Groovy script, the request event handler saves a value in the /event zone, from where the response event handler retrieves it:

def onMyConnectionHandlerRequest() {
    zput("/event/someState", "someValue");
}

def onMyConnectionHandlerResponse() {
    def value = zget("/event/someState");
    // value will contain "someValue"
}

A class-based connection handler should not update the /event zone because the onRequest() and onResponse() methods do not have an event associated with them. See Writing a class-based connection handler for more information.

Suppressing request processing

A connection handler request implementation can choose to prevent further connection request processing. If a connection request is suppressed in this way, no further connection handlers are executed and the request is not sent to the target resource. Connection response processing begins, starting with the preceding connection handler.

To suppress request processing, the connection handler request implementation places a value in the global context using any key starting with /connection/response/

If the connection handler request implementation sets any part of the response in the global context, it is deemed to have determined the complete response. It might be necessary for the connection handler to set several values, such as /connection/response/body, /connection/response/status and values under /connection/response/headers, to simulate a full response for the applicable protocol.

Exception handling

A connection handler can signal an exception situation by placing an instance of Exception in /connection/response/body, during either request or response processing. Connection handler implementations should be designed to use this mechanism to signal exceptions rather than throwing a runtime exceptions. Setting /connection/response/body suppresses any remaining connection request processing and response processing begins, starting with the preceding connection handler.

If an unexpected RuntimeException is thrown during connection handler processing processing, it is caught by the connection infrastructure and placed in /connection/response/body on behalf of the connection handler implementation.

The Exception contained in /connection/response/body can be examined or replaced by a connection handler response implementation. If /connection/response/body contains an Exception after connection handler response processing is complete, the exception is thrown to the application that is using the Connection API. The Exception is wrapped in an ConnectionException if it is not an instance of ConnectionException or IOException.

Considerations when streaming

Caution should be taken if a connection handler needs to examine a request or response body in streaming scenarios. If the connection handler begins to read an InputStream or Reader body, then the read data is effectively removed from the body. If necessary, replace the request body with a buffer containing the contents of the consumed the InputStream or Reader, or provide a wrapper stream.

If a connection handler replaces a request or response body with an instance of InputStream or Reader, the implementation of the new body can reference the /connection global context zone. The connection infrastructure ensures that the correct /connection context is made available when the application reads the stream. However the implementation of any other object used to represent a response body must not reference the /connection global context zone.

Connection handlers do not support direct OutputStream processing. If the application requests an output stream using Connection.getRequestBodyOutputStream() and a handler is configured, the request body is automatically collected in a buffer, and /connection/request/body contains an InputStream from which the request body can be read.

Writing a class-based connection handler

Class-based connection handler implementations must implement the zero.core.connection.handlers.Handler interface. This interface has two methods that must be implemented:

  • void onRequest()
  • void onResponse()

A class-based connection handler should not update the /event zone because the onRequest() and onResponse() methods do not have an event associated with them. The contents of the /event zone when a class-based connection handler is invoked belongs to the event handler that called the Connection API. However, a new instance of the Handler implementation is created for each connection request and the same instance is used for the corresponding response processing, so state can be carried between them using instance variables.

A class-based connection handler works with the contents /connection zone of the global context in the same way that an event-based connection handler does. For example, the following class-based connection handler sets the Accept header for the connection request:

package examples.handler;

import zero.core.connection.handlers.Handler;
import zero.core.context.GlobalContext;

public class AcceptTypeHandler implements Handler {

    public void onRequest() {
        GlobalContext.zput("/connection/request/headers/Accept", "text/*");
    }
        
    public void onResponse() {
        // Do nothing
    }
    
}
  • A class-based connection handler does not require any event handler registrations. To use a class-based connection handler, the full Java class name must be specified in the connection destination configuration. For more information, see Configuring connection handlers.
  • See the API reference for zero.core for a description of the zero.core.connection.handlers.Handler interface.

Example connection handler implementations

The following examples contain simple, event-based connection handler implementations.

Setting a request header

In the following example Groovy implementation of a connection handler, acceptText, the request handler sets the Accept header on each connection request before it is sent to the target service:

def onAcceptTextRequest() {
    zput("/connection/request/headers/Accept", "text/*");
}

def onAcceptTextResponse() {
    // Do nothing
}

If the acceptText implementation is saved as acceptText.groovy in the application app/scripts directory, then the following configuration makes the connection handler available for use in a connection destination configuration:

/config/handlers += [{
    "events" : ["acceptTextRequest", "acceptTextResponse"],
    "handler" : "acceptText.groovy" 
}]

The following configuration example shows how the acceptText connection handler could be associated with a connection destination:

/config/connection/destinations += {
    "http://www.projectzero.org/service.groovy" : {
        "handlers" : [{
            "name" : "acceptText"
        }]
    }
}

Selecting user ID and password for a request

As an example of how connection configuration might be used, consider a scenario in which an application makes requests to a service which must be authenticated using a dynamically determined user ID and password. The selectUserCredentials connection handler must select a user ID and password from a map supplied in the destination configuration and supply it to the http protocol configuration. The following example configuration shows how this handler might be configured for a connection destination:

/config/connection/destinations += {
    "http://www.projectzero.org/service.groovy*" : {
        "handlers" : [{
            "name" : "selectUserCredentials",
            "config" : {
                "credentials" : {
                    "gold" : {
                        "userid" : "user1",
                        "password" : "password1"
                    },
                    "silver" : {
                        "userid" : "user2",
                        "password" : "password2"
                    },
                    "bronze" : {
                        "userid" : "user3",
                        "password" : "password3"
                    }
                }
            }
        }]
    }
}

The selectUserCredentials connection handler implementation must:

  1. Identify the service level required for the current connection request; gold, silver or bronze. For this example, we will assume that the application has determined this when the user signed on and stored the result in the global context, as /user/level.
  2. Collect the corresponding user ID and password from the destination configuration.
  3. Set the userid and password protocol configuration parameters for the http protocol.

The following example Groovy script contains an implementation of the selectUserCredentials connection handler:

def onSelectUserCredentialsRequest() {
    // Read user level from /user zone
    def level = zget("/user/level");
    
    // Read user ID from connection handler configuration
    def userid = zget("/connection/configuration#credentials/" + level + "/userid");
    
    if (userid == null) {
    
        // No appropriate user ID specified so return a 403 status code to application.
        // By setting any part of /connection/response this handler prevents the
        // request from getting any further.
        zput("/connection/response/status" , "403")
        
    } else {
    
        // Set configured user ID and password on connection request
        zput("/connection/request/protocol/userid", userid);
        zput("/connection/request/protocol/password",
            zget("/connection/configuration#credentials/" + level + "/password")
            );
            
    }
}

def onSelectUserCredentialsResponse() {
    // Do nothing
}

The connection handler request implementation does the following:

  1. The user's service level, level, is read from where the application had previously recorded it in the global context.
  2. The level value is used to form a global context key, which used to read the associated userid value from the connection handler configuration.
  3. If no userid is configured to match the level value, the request handler sets the response status to simulate the HTTP 403 status code. By setting any part of the connection response, the request handler suppresses further request processing and the request will not be sent to the target service.
  4. If a userid is configured, it is used as the protocol configuration property, userid, and the corresponding password is copied from the connection handler configuration.

If the selectUserCredentials implementation is saved as selectUserCredentials.groovy in the application app/scripts directory, then the following configuration makes the connection handler available for use in a connection destination configuration:

/config/handlers += [{
    "events" : ["selectUserCredentialsRequest", "selectUserCredentialsResponse"],
    "handler" : "selectUserCredentials.groovy" 
}]

Version 1.1.30763