Broadcast on Broadcast off
The Documentation for Project Zero has moved. Please update your bookmarks to: http://www.projectzero.org/documentation/
Table of
Contents...
Hide

Project Zero Developer’s Guide

Concepts and components
Basic concepts overview
Event processing
Writing Java handlers
Writing Groovy handlers
Firing events
Global Context
Global Context reference
Application directory layout
Virtualized directories
Assemble
PHP
Features and configuration
Configuration
Debugging
Dependencies
Packaging
Application classpath
Logging and tracing
RESTful resources
RESTful documentation
File serving
Response rendering
Validators and validation
HTTP error handling
Calling a remote resource
Using the Connection API
Sending an email using EmailConnection
Configuring destinations
Configuring protocols
Configuring connection handlers
Creating a connection handler
Creating a custom protocol transport
Simple logging connection handlers
Protocol reference
Client programming with Dojo
Runtime options
Deployment modifications
HTTP configuration
SSL configuration
Proxy configuration
Extending the CLI
Security considerations
Authentication
OpenID authentication
Extending security
Security tokens
CSRF prevention support
Extending token support
Leveraging TAI
User service
File based user service
LDAP user service
Extending user service
Security Utilities
Leveraging XOREncoder
Extensions
Atom support
RSS support
JSON support
XMLEncoder
REST to SOAP extension
URIUtils
Developer Web tools
Database setup tools
Configuring data access
Common query patterns
Advanced query patterns
Update patterns
Local database transactions
Extending data access
Configuration vendor differences
PHP data access
Resource model
Configuring ZRM
Resource model declaration
Programmatic model API
HTTP REST API
A ZRM mini tutorial
Active content filtering support
Default filters
Custom filters
Runtime management
Management commands
Zero socket opener
Other extension modules
Amazon E-commerce service
Flickr service
WeatherZero forecast service
Wikipedia service
Reference
Zero command line interface
JavaDoc - Public API
JavaDoc - Public SPI
JavaDoc - All Classes

 

Creating a connection handler

This topic outlines how to create a new connection handler using the Handler SPI. For an overview of connection handlers and their configuration, see Configuring connection handlers.

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

Also review the Javadoc SPI reference for more information.

Overview

The Handler interface

Connection handler implementations must implement the zero.core.connection.handlers.Handler interface. This interface has two methods which must be implemented.

Handler
void onRequest()
void onResponse()

When a connection handler is configured for a destination, the onRequest() method is invoked during request processing and the onResponse() method is invoked during response processing.

The connection context

The onRequest() and onResponse() methods interact directly with the /connection zone of the GlobalContext. Details of the outbound request can be found using the following keys:

GlobalContext 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/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 will depend on the body supplied by the caller of the Connection API.

Details of the response can be found using the following keys:

GlobalContext keys
/connection/response/headers/ header_name List of values for response header.
/connection/response/status Response status String.
/connection/response/body Response body.

The response body may be any object type, including InputStream and Reader.

Considerations when streaming

Care 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. It may be necessary to 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 InputStream, Reader or similar streaming construct, the implementation of the new body should not reference the /connection GlobalContext zone.

Note connection handlers do not support OutputStream processing. If the caller requested a Connection.getRequestBodyOutputStream() and a handler is configured, the request body will be automatically be collected in a buffer and /connection/request/body will reference an InputStream from which the request body it can be read.

Reading handler configuration

Handler-specific configuration can be specified when a handler is registered with a connection destination, as described in Configuring connection handlers. When the Handler is invoked, the configuration is made available in the GlobalContext as a Map under the key /connection/configuration.

For example, consider the following destination configuration:

/config/connection/destinations += [{
        "name" : "http://localhost:8080/target.groovy",
        "handlers" : [{
                "class" : "examples.handler.SimpleMap2JSONHandler",
                "config" : {
                        "contentType" : "text/json; charset=UTF-8"
                }
        }]
}]

This configuration associates the handler implementation class "examples.handler.Map2JSONHandler" with the destination, "http://localhost:8080/target.groovy", and supplies some handler configuration. When the Handler is invoked, it will be able to find the configuration in the GlobalContext as follows:

/connection/configuration#contentType

For more information about destination configuration, see Configuring destinations.

Exception handling

Either onRequest() or onRespond() can signal an exception situation by placing an instance of Exception in /connection/response/body.

Implementations of onRequest() and onRespond() should be designed to use this mechanism to signal exceptions rather than throwing a RuntimeException. If an unexpected RuntimeException is thrown during onRequest() or onRespond() processing, it will be caught by the connection infrastructure and placed in /connection/response/body on behalf of the Handler implementation.

If /connection/response/body contains an Exception when onRequest() or onResponse() returns, response processing is carried out starting with the preceding connection handler. If onRequest() sets and an Exception response then any following connection handlers will not be invoked and the request will not be sent to the resource. The Exception contained in /connection/response/body can be examined or replaced by the onReponse() method of any remaining configured connection handlers.

If /connection/response/body contains an Exception after the connection handlers have completed their processing, it will eventually be thrown to the application when it uses the Connection API. The Exception will be wrapped in an ConnectionException if it is not an instance of ConnectionException or IOException.

Example handler implementation

To demonstrate the principles involved in writing a connection handler, we will use an example called SimpleMap2JSONHandler.

In this scenario an application using the Connection API works with request and response bodies as instances of Map but the target resources is a REST service using JSON syntax. During onRequest() the connection handler will convert the request Map sent by the application to a JSON request for the target service. In onResponse(), the JSON response from the service is decoded into a Map to be delivered to the application.

Here is the example implementation of onRequest():

public void onRequest() {
        try {
                
                // Examine request body
                Object body = GlobalContext.zget(GlobalContextURIs.Connection.Request.body, null);
                if (body instanceof Map) {
                        
                        // Replace Map body with JSON encoded string
                        String jsonString = Json.encode(body);
                        GlobalContext.zput(GlobalContextURIs.Connection.Request.body, jsonString);
                        
                        // Set Content-Type header
                        String contentType = GlobalContext.zget(GlobalContextURIs.Connection.configuration + "#contentType");
                        GlobalContext.zput(GlobalContextURIs.Connection.Request.headers + "/Content-Type", ListFactory.create(contentType)); 
                        
                }
                
        } catch (Exception e) {
                GlobalContext.zput(GlobalContextURIs.Connection.Response.body, e);
        }
}

The following is a summary of what this onRequest() method is doing:

  • The request body (if any) /connection/request/body key is read from the GlobalContext.
  • If the request body is an instance of Map:
    • /connection/request/body is replaced with a String representation of the Map using JSON syntax.
    • /connection/request/headers/Content-Type is set, using the contentType configuration property supplied to the Handler.
  • If any exceptional situation arises, an Exception is placed in /connection/response/body.

Here is the example implementation of onResponse():

public void onResponse() {
        try {
                
                // Look for content type header
                List<Object> contentTypeHdr = (List<Object>) GlobalContext.zget(GlobalContextURIs.Connection.Response.headers + "/Content-Type", null);
                
                String contentType = GlobalContext.zget(GlobalContextURIs.Connection.configuration + "#contentType");
                if (contentTypeHdr != null && contentTypeHdr.contains(contentType)) {
                        
                        Object body = GlobalContext.zget(GlobalContextURIs.Connection.Response.body, null);
                        String jsonString = DataUtilities.convertToString(body, "UTF8");                
                        Object decoded = Json.decode(jsonString);
                        
                        GlobalContext.zput(GlobalContextURIs.Connection.Response.body, decoded);
                        
                }
                
        } catch (Exception e) {
                GlobalContext.zput(GlobalContextURIs.Connection.Response.body, e);
        }
        
}

The following is a summary of what this onResponse() method is doing:

  • /connection/response/headers/Content-Type is examined and compared to the value of the contentType configuration property supplied to the Handler.
  • If the content type matches, /connection/response/body is replaced with a decoded form of the JSON representation (using a utility method in zero.core.connection.utils.DataUtilities to obtain a String value).
  • If any exceptional situation arises, an Exception is places in /connection/response/body.

Here is the remainder of the SimpleMap2JSONHandler:

package examples.handler;

import java.util.List;
import java.util.Map;
import zero.core.context.GlobalContext;
import zero.core.context.GlobalContextURIs;
import zero.core.connection.handlers.Handler;
import zero.core.connection.utils.DataUtilities;
import zero.json.Json;
import zero.util.ListFactory;

public class SimpleMap2JSONHandler implements Handler {
        
        // onRequest() and onResponse() go here    
        
}

To use this simple connection handler, it needs to be configured for a destination as in the following example configuration:

/config/connection/destinations += [{
        "name" : "http://localhost:8080/target.groovy",
        "handlers" : [{
                "class" : "examples.handler.SimpleMap2JSONHandler",
                "config" : {
                        "contentType" : "text/json; charset=UTF-8"
                }
        }]
}]

It should be be remembered that the SimpleMap2JSONHandler example is intended to illustrate the principles of writing a Handler implementation and may not be suitable for deployment in this form. In particular, it is worth noting that it relies on the "Content-Type" header having the exact value specified by the "contentType" configuration property and is case sensitive; that is it is not able to cope with responses containing "CONTENT-TYPE" or "content-type" headers.

r24 - 07 Feb 2008 - 20:06:43 - todkap
Syndicate this site RSS ATOM
Copyright 2007 © IBM Corporation | Privacy | Terms of Use | About this site